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

时间:2011-08-06 12:28:37

标签: python oop metaclass

在讨论元类时,the docs陈述:

  

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

我的问题是:假设我想在调用类时有自定义行为,例如缓存而不是创建新对象。我可以通过覆盖类的__new__方法来完成此操作。我什么时候想用__call__来定义元类?这种方法给出的是__new__无法实现的目标?

5 个答案:

答案 0 :(得分:19)

您问题的直接答案是:当您想要更多而不仅仅是自定义实例创建时,或者当您希望将所做的类与其如何分开时创建

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

有几个优点。

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

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

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

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

  5. 如果您不担心单一责任原则,肯定有很多情况可以自定义__new__

    但是还有其他一些用例必须在创建类时更早发生,而不是在创建实例时发生。当它们发挥作用时,必须使用元类。有关很多很好的例子,请参阅What are your (concrete) use-cases for metaclasses in Python?

答案 1 :(得分:14)

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

答案 2 :(得分:11)

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

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

答案 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

答案 4 :(得分:0)

我认为充实的Python 3版本的pyroscope答案可能方便有人复制,粘贴和破解(很可能是我,当我回到这个页面六个月后再次查找时)。它取自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