【问题标题】:Using the __call__ method of a metaclass instead of __new__?使用元类的 __call__ 方法而不是 __new__?
【发布时间】:2011-10-21 11:02:25
【问题描述】:

在讨论元类时,the docs 状态:

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

[编者注:这已从 3.3 的文档中删除。它在 3.2 中:Customizing class creation]

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

【问题讨论】:

  • 对于任何进入文档的人,很遗憾找不到该声明。
  • @Marine 在 3.3 中被移除。在 3.2 中:Customizing class creation

标签: python oop metaclass


【解决方案1】:

我认为一个充实的 Python 3 版本的 pyroscope 的答案可能会方便某人复制、粘贴和破解(可能是我,当我发现自己在 6 个月后再次访问此页面时)。取自this article

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 Metaprogramming tutorial

【讨论】:

    【解决方案2】:

    一个区别是,通过定义元类 __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__

    【讨论】:

    • 还要注意,如果MetaFoo.__call__() 方法调用super(MetaFoo, cls).__call__(*args, **kwargs)cls.__new__() 将被间接调用。
    • 顺便说一句,metaclass 属性在 python3 中消失了,现在使用class Simple1(object, metaclass = SimpleMeta1):...感谢python-3-patterns-idioms-test.readthedocs.io/en/latest/…
    【解决方案3】:

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

    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_1 super 中的第一个参数吗??
    • 感谢您的回答。我使用了部分示例代码,并提出了一个令我感到困惑的特定问题。我现在对这个话题感觉好多了。供您参考,问题在这里:stackoverflow.com/questions/56691487/…
    • 您介意我解释一下您的 cmets 并发布为我的问题的答案:stackoverflow.com/questions/56691487/… 吗?或者更好的是,您介意花一点时间在此处复制您的 cmets 并粘贴为链接问题的答案吗?我肯定会投赞成票。
    • 所以,我认为super(arg1, arg2) 会查看第二个输入参数的 MRO 以找到第一个输入参数,并将下一个类返回给它。但是rv = super(Meta_1, cls).__call__(*a, **kw),第二个参数的MRO(cls,或Class_1 )不包含第一个输入参数(Meta_1),你在Class_1的MRO中找不到Meta_1。所以我不明白为什么我们要调用type.__call__(Class_1)。这就是我问的原因。
    【解决方案4】:

    您的问题的直接答案是:当您想要做更多而不仅仅是自定义实例创建时,或者当您想要将类 做什么 与它的方式分开时已创建。

    查看我对Creating a singleton in Python 的回答以及相关讨论。

    有几个优点。

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

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

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

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

    如果您不担心单一责任原则,在很多情况下自定义 __new__ 的效果当然也不错。

    但还有其他用例必须在创建类时发生,而不是在创建实例时发生。当这些开始发挥作用时,元类是必要的。请参阅 What are your (concrete) use-cases for metaclasses in Python? 以获取大量优秀示例。

    【讨论】:

      【解决方案5】:

      这与生命周期阶段以及您可以访问的内容有关。 __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
      

      【讨论】:

      • 不!元类上的__new__ 发生在创建 class 而不是 instance 时。 __call__ 发生在 __new__ 在没有元类的情况下发生。
      • 哪里说__new__跟实例创建有关?
      • 我实际上是在询问类的__new__,而不是元类的__new__
      • 听起来你肯定在谈论类'__new__,而不是元类__new__
      • __new__ 的类(不是元类)在类的实例化时创建对象时被调用。如果您想返回之前创建的对象(例如单例),而不是重新创建新对象,这很有用。
      猜你喜欢
      • 2015-08-31
      • 2014-03-31
      • 2011-01-31
      • 2017-10-25
      • 2011-03-09
      • 2015-12-15
      • 2012-09-02
      • 2022-10-14
      • 1970-01-01
      相关资源
      最近更新 更多