关于python:使用元类的 __call__调用方法而不是 __new__方法?

Using the __call__ method of a metaclass instead of __new__?

在讨论元类时,docs声明:

You can of course also override other class methods (or add new
methods); for example defining a custom __call__() method in the
metaclass allows custom behavior when the class is called, e.g. not
always creating a new instance.

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


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

请参阅我在python中创建singleton的答案和相关讨论。

有几个优点。

  • 它允许您将类的工作与创建它的详细信息分开。元类和类各自负责一件事。

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

  • 子类可以覆盖它们的__new__方法中的行为,但元类上的__call__甚至不必调用__new__

  • 如果有安装工作,您可以在元类的__new__方法中进行,并且它只发生一次,而不是每次调用该类。

  • 当然,如果您不担心单一责任原则,那么定制__new__也可以发挥同样的作用。

    但是,还有其他用例必须在创建类时,而不是在创建实例时,更早地发生。正是在这些因素发挥作用的时候,元类才是必要的。看看您在Python中对元类的(具体)用例是什么?有很多很好的例子。


    一个区别是,通过定义元类__call__方法,您要求在类或子类的__new__方法获得调用机会之前调用它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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'>, (),{}

    注意,EDOCX1[5]从未被调用。相反,如果在没有元类的情况下定义Foo.__new__,则允许子类重写Foo.__new__

    当然,您可以将MetaFoo.__call__定义为调用cls.__new__,但这取决于您自己。通过拒绝这样做,可以防止子类调用它们的__new__方法。

    在这里,我看不到使用元类的显著优势。既然"简单胜于复杂",我建议使用__new__


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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)

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

    1
    2
    3
    class Meta_1(type): pass
    class Class_1(object):
        __metaclass__ = Meta_1

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

    1
    2
    3
    4
    5
    6
    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__()的伪实现是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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__()可以返回一个甚至没有被任何人触摸过的物体。以单例模式的这种方法为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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类型的对象时会发生什么情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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

    现在,使用类"EDOCX1"(27)方法来观察这个实现,以尝试完成相同的事情。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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)

    注意,上述实现即使在类上成功注册了一个singleton,也不会阻止调用__init__(),这在type.__call__()中隐式发生(如果没有指定,type是默认的元类)。这可能会导致一些不想要的影响:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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

    这是生命周期阶段和您可以访问的内容的问题。__call____new__之后被调用,并且在初始化参数被传递到__init__之前被传递,因此您可以对它们进行操作。尝试此代码并研究其输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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


    我认为一个完整的python 3版本的pyroscope的答案可能很方便有人复制、粘贴和破解(可能是我,当我发现自己在6个月后再次在这个页面上查找它时)。摘自本文:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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')

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ** 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

    同一篇文章强调的另一个重要资源是DavidBeazley的pycon 2013 python 3元编程教程。