通过学习《流畅的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
| 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')
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 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__ <[9]> ClassSix body <[500]> 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_z。MetaAleph.__init__ 方法签名中的cls指代要创建的类(例如ClassFive)。而inner_2函数签名中的self最终是指代我们在创建的类的实例(例如ClassFive类的实例)。
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 <[200]> deco_alpha <[4]> ClassFour body <[6]> ClassFive body <[500]> MetaAleph.__init__ <[9]> ClassSix body <[500]> MetaAleph.__init__ <[11]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 <[12]> ClassFour tests .............................. <[5]> ClassFour.method_y <[13]> ClassFive tests .............................. <[7]> ClassFive.__init__ <[600]> MetaAleph.__init__:inner_2 <[14]> ClassSix tests .............................. <[7]> ClassFive.__init__ <[600]> MetaAleph.__init__:inner_2 <[15]> evaltime_meta module end
|
注意,ClassSix 类没有直接引用MetaAleph类,但是却受到了影响,因为它是ClassFive的子类,进而也是MetaAleph类的实例,所以由MetaAleph.__init__ 方法初始化。