使用元类的 __call__ 方法而不是 __new__?

新手上路,请多包涵

在讨论元类时, 文档 指出:

您当然也可以覆盖其他类方法(或添加新方法);例如,在元类中定义自定义 __call__() 方法允许在调用类时自定义行为,例如,并不总是创建一个新实例。

[编者注:这已从 3.3 的文档中删除。它在 3.2 中: 自定义类创建]

我的问题是:假设我想在类被调用时有自定义行为,例如缓存而不是创建新对象。我可以通过覆盖类的 __new__ 方法来做到这一点。我什么时候想用 __call__ 来定义元类?这种方法提供了什么 __new__ 无法实现的?

原文由 Eli Bendersky 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 578
2 个回答

您的问题的直接答案是:当您想要做的 不仅仅是 自定义实例创建时,或者当您想要将类的 作用 与其创建方式分开时。

请参阅我对 Creating a singleton in Python 的回答和相关讨论。

有几个优点。

  1. 它允许您将类的 作用 与其创建方式的细节分开。元类和类各负责一件事。

  2. 您可以在元类中编写一次代码,并使用它来自定义多个类的调用行为,而不必担心多重继承。

  3. 子类可以覆盖其 __new__ 方法中的行为,但是 __call__ 在元类上甚至不必调用 __new__

  4. 如果有设置工作,你可以在元类的 __new__ 方法中进行,它只发生一次,而不是每次调用类时。

如果您不担心单一责任原则,那么在很多情况下定制 __new__ 也同样有效。

但是还有其他用例必须在创建类时更早发生,而不是在创建实例时发生。当这些发挥作用时,元类就很有必要了。请参阅 Python 中元类的(具体)用例是什么? 对于很多很好的例子。

原文由 agf 发布,翻译遵循 CC BY-SA 3.0 许可协议

当您仔细观察这些方法的执行顺序时,细微的差别会变得更加明显。

 class Meta_1(type):
    def __call__(cls, *a, **kw):
        print "entering Meta_1.__call__()"
        rv = super(Meta_1, cls).__call__(*a, **kw)
        print "exiting Meta_1.__call__()"
        return rv

class Class_1(object):
    __metaclass__ = Meta_1
    def __new__(cls, *a, **kw):
        print "entering Class_1.__new__()"
        rv = super(Class_1, cls).__new__(cls, *a, **kw)
        print "exiting Class_1.__new__()"
        return rv

    def __init__(self, *a, **kw):
        print "executing Class_1.__init__()"
        super(Class_1,self).__init__(*a, **kw)

请注意,上面的代码除了记录我们正在做的事情之外实际上并没有 任何事情。每个方法都遵循其父实现,即它的默认实现。因此,除了记录之外,它实际上就像您简单地声明了以下内容一样:

 class Meta_1(type): pass
class Class_1(object):
    __metaclass__ = Meta_1

现在让我们创建一个实例 Class_1

 c = Class_1()
# entering Meta_1.__call__()
# entering Class_1.__new__()
# exiting Class_1.__new__()
# executing Class_1.__init__()
# exiting Meta_1.__call__()

因此,如果 type 是 --- 的父级,我们可以想象 Meta_1 type.__call__() 伪实现:

 class type:
    def __call__(cls, *args, **kwarg):

        # ... a few things could possibly be done to cls here... maybe... or maybe not...

        # then we call cls.__new__() to get a new object
        obj = cls.__new__(cls, *args, **kwargs)

        # ... a few things done to obj here... maybe... or not...

        # then we call obj.__init__()
        obj.__init__(*args, **kwargs)

        # ... maybe a few more things done to obj here

        # then we return obj
        return obj

Notice from the call order above that Meta_1.__call__() (or in this case type.__call__() ) is given the opportunity to influence whether or not calls to Class_1.__new__() and Class_1.__init__() 最终做出来。在其执行过程中 Meta_1.__call__() 可能会返回一个甚至都没有被触及的对象。以单例模式的这种方法为例:

 class Meta_2(type):
    __Class_2_singleton__ = None
    def __call__(cls, *a, **kw):
        # if the singleton isn't present, create and register it
        if not Meta_2.__Class_2_singleton__:
            print "entering Meta_2.__call__()"
            Meta_2.__Class_2_singleton__ = super(Meta_2, cls).__call__(*a, **kw)
            print "exiting Meta_2.__call__()"
        else:
            print ("Class_2 singleton returning from Meta_2.__call__(), "
                    "super(Meta_2, cls).__call__() skipped")
        # return singleton instance
        return Meta_2.__Class_2_singleton__

class Class_2(object):
    __metaclass__ = Meta_2
    def __new__(cls, *a, **kw):
        print "entering Class_2.__new__()"
        rv = super(Class_2, cls).__new__(cls, *a, **kw)
        print "exiting Class_2.__new__()"
        return rv

    def __init__(self, *a, **kw):
        print "executing Class_2.__init__()"
        super(Class_2, self).__init__(*a, **kw)

让我们观察当反复尝试创建类型为 Class_2 的对象时会发生什么

a = Class_2()
# entering Meta_2.__call__()
# entering Class_2.__new__()
# exiting Class_2.__new__()
# executing Class_2.__init__()
# exiting Meta_2.__call__()

b = Class_2()
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped

c = Class_2()
# Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped

print a is b is c
True

现在观察这个实现,使用一个类 __new__() 方法来尝试完成同样的事情。

 import random
class Class_3(object):

    __Class_3_singleton__ = None

    def __new__(cls, *a, **kw):
        # if singleton not present create and save it
        if not Class_3.__Class_3_singleton__:
            print "entering Class_3.__new__()"
            Class_3.__Class_3_singleton__ = rv = super(Class_3, cls).__new__(cls, *a, **kw)
            rv.random1 = random.random()
            rv.random2 = random.random()
            print "exiting Class_3.__new__()"
        else:
            print ("Class_3 singleton returning from Class_3.__new__(), "
                   "super(Class_3, cls).__new__() skipped")

        return Class_3.__Class_3_singleton__

    def __init__(self, *a, **kw):
        print "executing Class_3.__init__()"
        print "random1 is still {random1}".format(random1=self.random1)
        # unfortunately if self.__init__() has some property altering actions
        # they will affect our singleton each time we try to create an instance
        self.random2 = random.random()
        print "random2 is now {random2}".format(random2=self.random2)
        super(Class_3, self).__init__(*a, **kw)

请注意,上述实现即使在类上成功注册了一个单例,也不会阻止 __init__() 被调用,这隐含地发生在 type.__call__() ( type 如果未指定,则为默认元类)。这可能会导致一些不良影响:

 a = Class_3()
# entering Class_3.__new__()
# exiting Class_3.__new__()
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.739298365475

b = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.247361634396

c = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.436144427555

d = Class_3()
# Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
# executing Class_3.__init__()
# random1 is still 0.282724600824
# random2 is now 0.167298405242

print a is b is c is d
# True

原文由 Michael Ekoka 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题