如何仅通过Python中的工厂来限制对象实例化

Kas*_*qui 3 python

有没有办法使用direct阻止对象实例化__init__,只允许通过工厂方法实例化?

Task() --> raise NotAllowedDirectInstantiation

Task.factory() --> return Task()

这可能还是我只是想多少?我知道我可以提供一些文档和警告,但只是想知道它是否可以完成.

例如.

class Task:
    def __init__():
        # Should not allow anyone to directly call Task()
        # but factory can call it

    @classmethod
    def factory_policy1(cls)
        # Do some stuff here
        # Raise events and stuff..
        return Task()

    @classmethod
    def factory_policy2(cls)
        # Do some policy 2 stuff here
        # Raise events and stuff..
        return Task()
Run Code Online (Sandbox Code Playgroud)

我看到了这个Python - 我如何强制使用工厂方法来实例化一个对象?但似乎是关于单身人士的讨论.

用例可能是当我们想要应用某些业务规则或在实例化新任务时引发某些事件但这些事件与Task对象没有密切关系时所以我们不希望混淆__init__那些事件.此工厂也不一定是一种类方法.它可能在别的地方.例如

# factories.py
from tasks.py import Task

def policy1_factory():
    # Do some cross-classes validations
    # Or raise some events like NewTaskCreatedEvent
    return Task()

def policy2_factory():
    # Do some policy 2 cross-classes validations
    # Or raise some events like NewTaskCreatedEvent
    return Task()
Run Code Online (Sandbox Code Playgroud)

我不希望其他开发人员做的是:

class TaskService:
    ...
    def create_task():
        # Yeah we could have HUGE warnings in __init__ doc
        # saying that do not initialize it directly, Use factory instead
        # but nothing stops them from accidentally doing so.
        # I would rather have raised an exception and crash then to allow this
        return Task()
        # should have done return Task.create()
Run Code Online (Sandbox Code Playgroud)

目的不是限制开发人员,而是进行一些安全检查.它甚至不需要引发异常,即使是一个警告就足够了但是必须有一个更清洁的方法来执行另外的文档或奇怪的猴子修补

希望它有意义

两种解决方案都很好:

@ spacedman的一个/sf/answers/2991490211/简单且限制性较小,可用作软警告

@ marii的一个/sf/answers/2991517581/更严格,如果我想要一个硬块就会使用它

谢谢

Mar*_*riy 9

正如其他人所说,禁止__init__不是最好的选择.我不推荐它.但是,您可以使用元类来实现此目的.考虑这个例子:

def new__init__(self):
    raise ValueError('Cannot invoke init!')

class Meta(type):
    def __new__(cls, name, bases, namespace):
        old_init = namespace.get('__init__')
        namespace['__init__'] = new__init__
        def factory_build_object(cls_obj, *args, **kwargs):
            obj = cls_obj.__new__(cls_obj, *args, **kwargs)
            return old_init(obj, *args, **kwargs)
        namespace['factory_build_object'] = classmethod(factory_build_object)
        return super().__new__(cls, name, bases, namespace)


class Foo(metaclass=Meta):
    def __init__(self, *args, **kwargs):
        print('init')


Foo.factory_build_object()
# Foo()
Run Code Online (Sandbox Code Playgroud)

如果取消注释它将引发的最后一行ValueError.这里我们在Meta的类构造中添加魔法工厂方法init.我们保存对旧init方法的引用,并将其替换为引发的方法ValueError.


Spa*_*man 5

这是一个可以简单规避的方法:

class Task(object):
    def __init__(self, direct=True):
        # Should not allow anyone to directly call Task()
        # but factory can call it
        if direct:
            raise ValueError
    @classmethod
    def factory(cls):
        # Do some stuff here        
        return Task(direct=False)
Run Code Online (Sandbox Code Playgroud)

因此classy.py

>>> import classy
Run Code Online (Sandbox Code Playgroud)

直接实例化时出现错误:

>>> t = classy.Task()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "classy.py", line 6, in __init__
    raise ValueError
ValueError
Run Code Online (Sandbox Code Playgroud)

通过工厂方法没有错误:

>>> t = classy.Task.factory()
Run Code Online (Sandbox Code Playgroud)

direct=False但通过传递给构造函数来规避:

>>> t = classy.Task(direct=False)
Run Code Online (Sandbox Code Playgroud)

我怀疑任何其他方法也都是可以规避的,只是稍微不那么容易。你希望人们在打破它之前尝试多努力?看来确实不值得...