含元类、类装饰器的Python代码执行顺序

通过学习《流畅的Python》这本书的第21章:类元编程,我算是系统地理清了Python代码的执行顺序,现在看到该书作者的这几个举例,我才算是真正明白。下面这个 evaltime.py 脚本值得多看几遍,加深理解。

元类基础知识

元类是制造类的工厂,不过不是函数,而是类。

根据Python对象模型,类是对象,因此类肯定是另外某个类的实例。默认情况下,Python中的类是type类的实例。也就是说,type是大多数内置的类和用户定义的类的元类

1
2
3
4
5
6
7
8
9
10
11
12
>>> 'spam'.__class__ 
<class 'str'>
>>> str.__class__
<class 'type'>

>>> LineItem.__class__
<class 'type'>
>>> type.__class__
<class 'type'>

>>> object.__class__
<class 'type'>

为了避免无限回溯,type是其自身的实例。

没有说 str 或 LineItem 继承自type。而是说,str和LineItem是type的实例。这两个类是object的子类。

image-20250222150305390

两个示意图都是正确的。左边的示意图强调 str、type和LineItem是object的子类。右边的示意图则清楚地表明str、object和LineItem是type的实例,因为它们都是类

object类和type类之间的关系很独特:object是type的实例,而type是object的子类。这种关系很“神奇”,无法使用Python代码表述,因为定义其中一个之前另一个必须存在。type是自身的实例这一点也很神奇。

除了type,标准库中还有一些别的元类,例如ABCMeta和Enum。如下述代码片段所示,collections.Iterable所属的类是abc.ABCMeta。Iterable是抽象类,而ABCMeta不是——不管怎样,Iterable是ABCMeta的实例:

1
2
3
4
5
6
7
8
9
>>> import collections 
>>> collections.Iterable.__class__
<class 'abc.ABCMeta'>

>>> import abc
>>> abc.ABCMeta.__class__
<class 'type'>
>>> abc.ABCMeta.__mro__
(<class 'abc.ABCMeta'>, <class 'type'>, <class 'object'>)

向上追溯,ABCMeta最终所属的类也是type。所有类都直接或间接地是type的实例,不过只有元类同时也是type的子类。若想理解元类,一定要知道这种关系:元类(如ABCMeta)从type类继承了构建类的能力。

image-20250222150607587

所有类都是type的实例,但是元类还是type的子类,因此可以作为制造类的工厂。


理解元类计算时间的demo

其中demo中用到的代码:

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
54
55
# evaltime_meta.py
from evalsupport import deco_alpha
from evalsupport import MetaAleph

print('<[1]> evaltime_meta module start')


@deco_alpha
class ClassThree():
print('<[2]> ClassThree body')

def method_y(self):
print('<[3]> ClassThree.method_y')


class ClassFour(ClassThree):
print('<[4]> ClassFour body')

def method_y(self):
print('<[5]> ClassFour.method_y')


# ClassFive 是 MetaAleph 元类的实例
class ClassFive(metaclass=MetaAleph):
print('<[6]> ClassFive body')

def __init__(self):
print('<[7]> ClassFive.__init__')

def method_z(self):
print('<[8]> ClassFive.method_z')


class ClassSix(ClassFive):
print('<[9]> ClassSix body')

def method_z(self):
print('<[10]> ClassSix.method_z')


if __name__ == '__main__':
print('<[11]> ClassThree tests', 30 * '.')
three = ClassThree()
three.method_y()
print('<[12]> ClassFour tests', 30 * '.')
four = ClassFour()
four.method_y()
print('<[13]> ClassFive tests', 30 * '.')
five = ClassFive()
five.method_z()
print('<[14]> ClassSix tests', 30 * '.')
six = ClassSix()
six.method_z()

print('<[15]> evaltime_meta module end')

场景1:evaltime_meta.py 被当做模块导入:

1
2
3
4
5
6
7
8
9
10
11
12
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body
<[200]> deco_alpha
<[4]> ClassFour body
<[6]> ClassFive body
<[500]> MetaAleph.__init__ # 与场景1的关键区别是,创建ClassFive时调用了MetaAleph.__init__方法。
<[9]> ClassSix body
<[500]> MetaAleph.__init__ # 创建ClassFive的子类ClassSix时也调用了MetaAleph.__init__方法。
<[15]> evaltime_meta module end

from evalsupport import deco_alpha在导入 deco_alpha 时,会执行 evalsupport 的所有顶层代码,所以有了上面结果的前3个打印输出。

编写元类时,通常会把self参数改成cls。例如,在上述元类的 __init__ 方法中,把第一个参数命名为cls能清楚地表明要构建的实例是类。__init__方法的定义体中定义了inner_2函数,然后将其绑定给cls.method_zMetaAleph.__init__ 方法签名中的cls指代要创建的类(例如ClassFive)。而inner_2函数签名中的self最终是指代我们在创建的类的实例(例如ClassFive类的实例)。


场景2:执行evaltime_meta.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body # class ClassThree():有被装饰器 deco_alpha 修饰,执行完类后会执行装饰器函数。
<[200]> deco_alpha
<[4]> ClassFour body # class ClassFour(ClassThree): 执行时,虽然继承了被装饰的ClassThree,但是ClassFour不会继承装饰器函数,因此不会ClassFour执行后,不会执行装饰器函数。
<[6]> ClassFive body # class ClassFive(metaclass=MetaAleph):执行后,需要执行元类的init函数,完成实例化。
<[500]> MetaAleph.__init__
<[9]> ClassSix body # class ClassSix(ClassFive):继承ClassFive,所以会继承元类,执行完ClassSix后执行元类。
<[500]> MetaAleph.__init__
<[11]> ClassThree tests ..............................
<[300]> deco_alpha:inner_1 # ClassThree被装饰器修饰而更改了method_y函数。
<[12]> ClassFour tests ..............................
<[5]> ClassFour.method_y # 虽ClassFour是ClassThree的子类,但是没有像ClassThree依附装饰器而更改了method_y函数。
<[13]> ClassFive tests ..............................
<[7]> ClassFive.__init__
<[600]> MetaAleph.__init__:inner_2 # ClassFive是MetaAleph元类的实例,在MetaAleph的init函数中把method_z绑定为inner_2
<[14]> ClassSix tests ..............................
<[7]> ClassFive.__init__ # ClassSix继承了ClassFive,所以six=ClassSix()实例化会执行ClassFive的init函数
<[600]> MetaAleph.__init__:inner_2 # 同理,作为父类的ClassFive是MetaAleph元类的实例,所以ClassSix也是。
<[15]> evaltime_meta module end

注意,ClassSix 类没有直接引用MetaAleph类,但是却受到了影响,因为它是ClassFive的子类,进而也是MetaAleph类的实例,所以由MetaAleph.__init__ 方法初始化。