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

时间:2017-03-11 12:51:06

标签: python

有没有办法使用直接__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()

我看到了Python - how do I force the use of a factory method to instantiate an object?,但似乎是关于单身人士的讨论。

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

__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()

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

希望它有意义

两种解决方案都很好:

@ spacedman的一个https://stackoverflow.com/a/42735574/4985585很简单,限制性较小,可用作软警告

@ marii的一个https://stackoverflow.com/a/42735965/4985585更严格,如果我想要一个硬块就会使用它

由于

3 个答案:

答案 0 :(得分:7)

正如其他人所说,禁止__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()

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

答案 1 :(得分:3)

这是一种简单易行的方法:

var data = JsonConvert.DeserializeObject<Rootobject>("YourJsonString");
var latLng = data.results[0].locations[0].latLng;
var lat = latLng.lat;
var lng = latLng.lng;

所以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)

classy.py

直接实例化时出错:

>>> import classy

通过工厂方法没有错误:

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

但是通过将>>> t = classy.Task.factory() 传递给构造函数来规避:

direct=False

我怀疑任何其他方法也是可以绕过的,只是稍微不那么容易。在破坏它之前,你希望人们有多难?它真的不值得......

答案 2 :(得分:2)

我不知道这是否符合您的需要。 为了它的价值,您可以在工厂函数的范围内定义Task类:

def factory():
    class Task:
        pass
    return Task()

正如@JonClements指出的那样,尽管如此:

  

当然,一旦你有了它的实例,你总是可以劫持它的类型,例如:Task = type(factory())然后my_task = Task() ......