关于python:元类以及何时/如何调用函数

Metaclasses and when/how functions are called

我正在尝试学习在Python3中元类是如何工作的。我想知道的是:调用了哪些函数,按什么顺序,以及它们的签名和返回。

例如,我知道当用参数metaclass, name_of_subclass, bases实例化一个具有元类的类时,会调用__prepare__,并返回一个表示已实例化对象未来命名空间的字典。

我觉得我很理解__prepare__在这个过程中的步骤。我不知道的是__init____new____call__。他们的论点是什么?他们会返回什么?他们是如何互相呼叫的,或者一般来说,整个过程是如何进行的?目前,我一直在理解何时调用__init__

下面是一些我一直在纠结的代码来回答我的问题:

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
#!/usr/bin/env python3

class Logged(type):

    @classmethod
    def __prepare__(cls, name, bases):
        print('In meta __prepare__')
        return {}

    def __call__(subclass):
        print('In meta __call__')
        print('Creating {}.'.format(subclass))
        return subclass.__new__(subclass)

    def __new__(subclass, name, superclasses, attributes, **keyword_arguments):
        print('In meta __new__')
        return type.__new__(subclass, name, superclasses, attributes)

    def __init__(subclass, name, superclasses, attributes, **keyword_arguments):
        print('In meta __init__')

class Thing(metaclass = Logged):

    def __new__(this, *arguments, **keyword_arguments):
        print('In sub __new__')
        return super(Thing, this).__new__(this)

    def __init__(self, *arguments, **keyword_arguments):
        print('In sub __init__')

    def hello(self):
        print('hello')

def main():
    thing = Thing()
    thing.hello()

if __name__ == '__main__':
    main()

通过这个和一些谷歌搜索,我知道__new__实际上是一个静态方法,它返回某个对象的一个实例(通常是定义__new__的对象,但并不总是这样),并且在创建该实例时调用__init__。根据这个逻辑,我不明白为什么不叫Thing.__init__()。有人能照亮吗?

这段代码的输出会打印"hello",因此正在创建一个实例,这会进一步混淆init。输出结果如下:

1
2
3
4
5
6
7
In meta __prepare__
In meta __new__
In meta __init__
In meta __call__
Creating <class '__main__.Thing'>
In sub __new__
hello

任何帮助理解元类的帮助都将不胜感激。我读了很多教程,但我错过了其中的一些细节。


首先:__prepare__是可选的,如果只返回默认的{}空字典,则不需要提供实现。

元类的工作方式与类完全相同,即当您调用它们时,它们会生成一个对象。类和元类都是工厂。区别在于,元类在调用时生成类对象,类在调用时生成实例。

类和元类都定义了默认的__call__实现,这基本上可以做到:

  • 调用self.__new__生成新对象。
  • 如果该新对象是具有此属性的self/a类的实例元类,然后在该对象上也调用__init__
  • 您生成了自己的__call__实现,它没有实现第二步,这就是为什么从未调用Thing.__init__的原因。

    您可能会问:但是__call__方法是在元类上定义的。这是正确的,所以当使用Thing()调用类时,调用的方法正是这个方法。所有特殊的方法(以__开始和结束)都在类型上被调用(例如type(instance)是类,type(class)是元类),这正是因为python具有来自元类的实例的多级层次结构;类本身的__call__方法用于使实例可调用。对于metaclass()调用,提供__call__实现的是type对象本身。没错,元类同时也是type的子类和实例。

    在编写元类时,如果希望自定义调用该类时所发生的事情,则只应实现__call__。否则将其保留在默认实现中。

    如果我从您的元类中删除__call__方法(并忽略__prepare__方法),那么Thing.__init__再次被调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    >>> class Logged(type):
    ...     def __new__(subclass, name, superclasses, attributes, **keyword_arguments):
    ...         print('In meta __new__')
    ...         return type.__new__(subclass, name, superclasses, attributes)
    ...     def __init__(subclass, name, superclasses, attributes, **keyword_arguments):
    ...         print('In meta __init__')
    ...
    >>> class Thing(metaclass = Logged):
    ...     def __new__(this, *arguments, **keyword_arguments):
    ...         print('In sub __new__')
    ...         return super(Thing, this).__new__(this)
    ...     def __init__(self, *arguments, **keyword_arguments):
    ...         print('In sub __init__')
    ...     def hello(self):
    ...         print('hello')
    ...
    In meta __new__
    In meta __init__
    >>> thing = Thing()
    In sub __new__
    In sub __init__


    在元类的__call__方法中,您只调用Thing__new__,而不调用__init__。似乎__call__的默认行为是调用这两个类,正如我们调用元类的继承__call__时所看到的:

    1
    2
    3
    4
        def __call__(subclass):
            print('In meta __call__')
            print('Creating {}.'.format(subclass))
            return super().__call__(subclass)

    印刷品:

    1
    2
    3
    Creating <class '__main__.Thing'>.
    In sub __new__
    In sub __init__