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


通过学习《流畅的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的子类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bUclo0Gy-1586592213066)(../../../markdown_pic/book2_metaclass1.png)]
两个示意图都是正确的。左边的示意图强调 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类继承了构建类的能力。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xjkoY5M1-1586592213094)(../../../markdown_pic/book2_metaclass2.png)]
所有类都是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_zMetaAleph.__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__ 方法初始化。

我的笔记


发布了91 篇原创文章 · 获赞 47 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_31362767/article/details/105454232