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

Eli*_*sky 35 python oop metaclass

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

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

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

agf*_*agf 22

直接回答你的问题是:当你想要做更多的不仅仅是定制实例创建,或当你想分开全班确实从它是如何创建的.

请参阅我在Python中创建单例的答案以及相关的讨论.

有几个优点.

  1. 它允许您将类的功能与创建它的详细信息分开.元类和类都负责一件事.

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

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

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

__new__如果您不担心单一责任原则,肯定有很多情况下定制工作也是如此.

但是,有些其他用例必须在创建类时更早发生,而不是在创建实例时发生.当它们发挥作用时,必须使用元类.请参阅Python中元类的(具体)用例是什么?有很多很好的例子.


unu*_*tbu 15

一个区别是,通过定义元类__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'>, (),{}
Run Code Online (Sandbox Code Playgroud)

请注意,SubFoo.__new__永远不会被调用.相反,如果您定义Foo.__new__没有元类,则允许子类重写Foo.__new__.

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

我没有看到在这里使用元类的强大优势.而且由于"简单比复杂更好",我建议使用__new__.

  • 另请注意,如果`MetaFoo .__ call __()`方法调用`super(MetaFoo,cls).__ call __(*args,**kwargs)`,则会间接调用`cls .__ new __()`. (3认同)

Mic*_*oka 13

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

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)
Run Code Online (Sandbox Code Playgroud)

请注意,除了记录我们正在做的事情之外,上面的代码实际上并没有任何事情.每种方法都遵循其父实现,即其默认值.所以除了记录它之外,就好像你只是简单地声明了如下内容:

class Meta_1(type): pass
class Class_1(object):
    __metaclass__ = Meta_1
Run Code Online (Sandbox Code Playgroud)

现在让我们创建一个实例 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__()
Run Code Online (Sandbox Code Playgroud)

因此,如果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
Run Code Online (Sandbox Code Playgroud)

从上面的呼叫订单中注意到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)
Run Code Online (Sandbox Code Playgroud)

让我们观察一下重复尝试创建类型对象时会发生什么 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
Run Code Online (Sandbox Code Playgroud)

现在使用类' __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)
Run Code Online (Sandbox Code Playgroud)

请注意,即使在类上成功注册单例,上述实现也不会阻止__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
Run Code Online (Sandbox Code Playgroud)

  • 很好的解释!!谢谢! (2认同)

Chr*_*ris 8

我认为一个充实的 Python 3 版本的pyroscope 的答案可能很方便有人复制、粘贴和破解(可能是我,当我发现自己在 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')
Run Code Online (Sandbox Code Playgroud)

输出:

** 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
Run Code Online (Sandbox Code Playgroud)

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