文章目录
- 1. 元类基础知识
- 2.理解元类计算时间的demo
- 场景1:evaltime_meta.py 被当做模块导入:
- 场景2:执行evaltime_meta.py:
通过学习《流畅的Python》这本书的第21章:类元编程,我算是系统地理清了Python代码的执行顺序,以前工作和学习中涉及元类、类装饰器的很少,所以自认为对Python代码执行顺序比较清楚,直到现在看到该书作者的这几个举例,我才算是真正明白。下面这个evaltime.py脚本值得多看几遍,加深理解。
1. 元类基础知识
元类是制造类的工厂,不过不是函数,而是类。
根据Python对象模型,类是对象,因此类肯定是另外某个类的实例。默认情况下,Python中的类是type类的实例。也就是说,type是大多数内置的类和用户定义的类的元类:
>>> '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的子类。
两个示意图都是正确的。左边的示意图强调 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的实例:
>>> 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类继承了构建类的能力。
所有类都是type的实例,但是元类还是type的子类,因此可以作为制造类的工厂。
2.理解元类计算时间的demo
其中demo中用到的代码: evalsupport .py
# 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 被当做模块导入:
<[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_z
。MetaAleph.__init__
方法签名中的cls指代要创建的类(例如ClassFive)。而inner_2函数签名中的self最终是指代我们在创建的类的实例(例如ClassFive类的实例)。
场景2:执行evaltime_meta.py:
<[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__
方法初始化。