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

73

讨论元类时,文档中说明:

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

[编辑注:这段内容已在3.3中从文档中删除。在3.2中可以找到此处:自定义类创建]

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


3
对于任何查看文档的人来说,不幸的是,这个声明无论在哪里都找不到。 - Marine Galantin
2
@Marine 它在3.3中被移除了。这里是3.2中的链接:自定义类创建 - wjandrea
6个回答

44
直接回答你的问题:当你想要做的不仅仅是自定义实例创建时,或者当你想要将类的动作与创建类的方式分开时。
请参见我在Creating a singleton in Python上的回答和相关讨论。
有几个好处。
  1. 它允许您将类的动作与创建类的细节分离。元类和类各自负责一件事。

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

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

  4. 如果有设置工作,您可以在元类的__new__方法中执行此操作,它仅发生一次,而不是每次调用类时都会执行。

当你只关心单一职责原则时,当然有很多情况下自定义__new__同样可行。
但是还有其他用例需要在创建类时而不是在创建实例时发生。当这些情况出现时,就需要使用元类。请参见What are your (concrete) use-cases for metaclasses in Python?以获取大量优秀示例。

44

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

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__()
因此,如果typeMeta_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

注意从调用顺序中可以看出,Meta_1.__call__()(或在此情况下为type.__call__())有机会影响是否最终调用Class_1.__new__()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

这是一个很好的答案。在你的 Meta_1.__call__ 中,你有 rv = super(Meta_1, cls).__call__(*a, **kw)。你能解释一下为什么 Meta_1super 中的第一个参数吗? - Kid_Learning_C
谢谢您的回答。我已经使用了示例代码的一部分,并提出了一个我感到困惑的具体问题。现在我对这个主题感觉好多了。供您参考,问题在这里:https://dev59.com/pLTma4cB1Zd3GeqP9aCo?noredirect=1#comment99954766_56691487 - Kid_Learning_C
你介意我将你的评论改写一下,然后作为我的问题的答案发布在这里吗:https://dev59.com/pLTma4cB1Zd3GeqP9aCo?noredirect=1?或者更好的是,你介意花一分钟将你的评论复制到这里,并将其作为链接问题的答案粘贴在这里吗?我肯定会点赞的。 - Kid_Learning_C
所以,我认为super(arg1,arg2)会查找第二个输入参数的MRO以找到第一个输入参数,并返回下一个类。但是rv = super(Meta_1,cls).__call__(*a, **kw),第二个参数(clsClass_1)的MRO不包含第一个输入参数(Meta_1),你无法在Class_1的MRO中找到Meta_1 。所以我不明白为什么要调用type.__call__(Class_1)。这就是我提出问题的原因。 - Kid_Learning_C

19

一个区别在于,通过定义元类的__call__方法,您要求它在任何类或子类的__new__方法被调用之前先被调用。

class MetaFoo(type):
    def __call__(cls,*args,**kwargs):
        print('MetaFoo: {c},{a},{k}'.format(c=cls,a=args,k=kwargs))

class Foo(object):
    __metaclass__=MetaFoo

class SubFoo(Foo):
    def __new__(self,*args,**kwargs):
        # This never gets called
        print('Foo.__new__: {a},{k}'.format(a=args,k=kwargs))

 sub=SubFoo()
 foo=Foo()

 # MetaFoo: <class '__main__.SubFoo'>, (),{}
 # MetaFoo: <class '__main__.Foo'>, (),{}

注意到SubFoo.__new__从未被调用。相反,如果你定义了没有元类的Foo.__new__,那么你允许子类重写Foo.__new__

当然,你可以定义MetaFoo.__call__来调用cls.__new__,但这取决于你。通过拒绝这样做,你可以防止子类调用其__new__方法。

我不认为在这里使用元类有明显的优势。而且,“简单比复杂好”,我建议使用__new__


3
请注意,如果MetaFoo.__call__()方法调用了super(MetaFoo, cls).__call__(*args,**kwargs),那么间接调用cls.__new __() - martineau
顺便提一下,在Python3中,__metaclass__属性已经被移除了,现在应该使用class Simple1(object, metaclass = SimpleMeta1):...谢谢https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html#the-metaclass-hook-in-python-3 - ThorSummoner

13

我认为对pyroscope答案进行深入阐述的Python 3版本可能会对某些人有用,可以复制、粘贴和修改(可能是我,在6个月后再次查看此页面时)。它来源于这篇文章

class Meta(type):

     @classmethod
     def __prepare__(mcs, name, bases, **kwargs):
         print('  Meta.__prepare__(mcs=%s, name=%r, bases=%s, **%s)' % (
             mcs, name, bases, kwargs
         ))
         return {}

     def __new__(mcs, name, bases, attrs, **kwargs):
         print('  Meta.__new__(mcs=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
             mcs, name, bases, ', '.join(attrs), kwargs
         ))
         return super().__new__(mcs, name, bases, attrs)

     def __init__(cls, name, bases, attrs, **kwargs):
         print('  Meta.__init__(cls=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
             cls, name, bases, ', '.join(attrs), kwargs
         ))
         super().__init__(name, bases, attrs)

     def __call__(cls, *args, **kwargs):
         print('  Meta.__call__(cls=%s, args=%s, kwargs=%s)' % (
             cls, args, kwargs
         ))
         return super().__call__(*args, **kwargs)

print('** Meta class declared')

class Class(metaclass=Meta, extra=1):

     def __new__(cls, myarg):
         print('  Class.__new__(cls=%s, myarg=%s)' % (
             cls, myarg
         ))
         return super().__new__(cls)

     def __init__(self, myarg):
         print('  Class.__init__(self=%s, myarg=%s)' % (
             self, myarg
         ))
         self.myarg = myarg
         super().__init__()

     def __str__(self):
         return "<instance of Class; myargs=%s>" % (
             getattr(self, 'myarg', 'MISSING'),
         )

print('** Class declared')

Class(1)
print('** Class instantiated')

输出:

** Meta class declared
  Meta.__prepare__(mcs=<class '__main__.Meta'>, name='Class', bases=(), **{'extra': 1})
  Meta.__new__(mcs=<class '__main__.Meta'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
  Meta.__init__(cls=<class '__main__.Class'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
** Class declared
  Meta.__call__(cls=<class '__main__.Class'>, args=(1,), kwargs={})
  Class.__new__(cls=<class '__main__.Class'>, myarg=1)
  Class.__init__(self=<instance of Class; myargs=MISSING>, myarg=1)
** Class instantiated

同一篇文章强调的另一个很棒的资源是David Beazley的PyCon 2013 Python 3元编程教程


1

这是生命周期阶段和你所能访问的内容的问题。__call____new__之后被调用,并传递初始化参数,然后再传递给__init__,因此您可以操纵它们。尝试运行以下代码并研究其输出:

class Meta(type):
    def __new__(cls, name, bases, newattrs):
        print "new: %r %r %r %r" % (cls, name, bases, newattrs,)
        return super(Meta, cls).__new__(cls, name, bases, newattrs)

    def __call__(self, *args, **kw):
        print "call: %r %r %r" % (self, args, kw)
        return super(Meta, self).__call__(*args, **kw)

class Foo:
    __metaclass__ = Meta

    def __init__(self, *args, **kw):
        print "init: %r %r %r" % (self, args, kw)

f = Foo('bar')
print "main: %r" % f

2
不!在元类上的__new__发生在创建_class_时,而不是实例。当没有元类时,__call__会在__new__发生时发生。 - agf
1
我在哪里说__new__与实例创建有关? - pyroscope
4
жҲ‘е®һйҷ…дёҠжҳҜеңЁиҜўй—®зұ»зҡ„__new__пјҢиҖҢдёҚжҳҜе…ғзұ»зҡ„__new__гҖӮ - Eli Bendersky
这确实听起来像是在谈论类的__new__而不是元类的__new__ - agf
当类的实例化创建对象时,会调用该类(而不是元类)的__new__方法。如果您想要返回已经创建过的对象(例如单例),而不是重新创建一个新对象,则此方法非常有用。 - Nickpick
在我看来,此答案相关且直接。寄存器模式似乎最自然地适用于元类中,正如其他答案所建议的那样,因此我也认为OP是在询问元类__new __()。有时您甚至需要元类__prepare__方法更早地介入实例化过程。这篇文章是一个相当好的概述,尽管它可能需要一个应用实例。 - Chris

0
在问题中给出的特定示例中,覆盖元类中的__call__比在类中覆盖__new__更好。
假设我想在类被调用时具有自定义行为,例如缓存而不是创建新对象。
  1. 如果缓存的目的是为了提高效率,那么缓存 __new__ 的结果是不理想的,因为无论如何都会执行 __init__ (数据模型:基本定制)。例如:

    from functools import lru_cache
    
    class MyClass:
    
        @lru_cache(maxsize=None)
        def __new__(cls, *args, **kwargs):
            return super().__new__(cls)
    
        def __init__(self, ...)
            "总是被执行。即使在缓存命中时也是如此。"
    
  2. 仅当 __init__ 对已初始化的实例没有明显影响时,缓存 __new__ 的结果才是可行的。否则,在缓存对象上执行 __init__ 可能会对其其他引用产生烦人的副作用

  3. 在元类级别进行缓存既避免了 1. 的性能问题,也避免了 2. 的正确性问题。例如:

    from functools import lru_cache
    
    class CachedInstances(type):
    
        @lru_cache(maxsize=None)
        def __call__(cls, *args, **kwargs):
            return super().__call__(*args, **kwargs)
    
    class MyClass(metaclass=CachedInstances):
    
        def __init__(self, ...)
            "仅在缓存未命中时执行。"
    
请注意,Michael Ekoka的回答已经提到了由于在覆盖__new__方法中重复执行__init__方法(如我的第2项)可能会出现的不良影响。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接