- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
21.4 元类基础知识
元类是制造类的工厂,不过不是函数(如示例 21-2 中的 record_factory),而是类。图 21-1 使用机器和小怪兽图示法描述元类,可以看出,元类是生产机器的机器。
图 21-1:元类是用于构建类的类
根据 Python 对象模型,类是对象,因此类肯定是另外某个类的实例。默认情况下,Python 中的类是 type 类的实例。也就是说,type 是大多数内置的类和用户定义的类的元类:
>>> 'spam'.__class__ <class 'str'> >>> str.__class__ <class 'type'> >>> from bulkfood_v6 import LineItem >>> LineItem.__class__ <class 'type'> >>> type.__class__ <class 'type'>
为了避免无限回溯,type 是其自身的实例,如最后一行所示。
注意,我没有说 str 或 LineItem 继承自 type。我的意思是,str 和 LineItem 是 type 的实例。这两个类是 object 的子类。图 21-2 可能有助于你理清这个奇怪的现象。
图 21-2:两个示意图都是正确的。左边的示意图强调 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 类继承了构建类的能力。图 21-3 对这种至关重要的关系做了图解。
图 21-3:Iterable 是 object 的子类,是 ABCMeta 的实例。object 和 ABCMeta 都是 type 的实例,但是这里的重要关系是,ABCMeta 还是 type 的子类,因为 ABCMeta 是元类。示意图中只有 Iterable 是抽象类
我们要抓住的重点是,所有类都是 type 的实例,但是元类还是 type 的子类,因此可以作为制造类的工厂。具体来说,元类可以通过实现 __init__ 方法定制实例。元类的 __init__ 方法可以做到类装饰器能做的任何事情,但是作用更大,如接下来的练习所示。
理解元类计算时间的练习
我们对 21.3 节的练习做些改动,evalsupport.py 模块与示例 21-7 一样,不过现在主脚本变成 evaltime_meta.py 了,如示例 21-10 所示。
示例 21-10 evaltime_meta.py:ClassFive 是 MetaAleph 元类的实例
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')
同样,请拿出纸和笔,按顺序写出下述两个场景中输出的序号标记 <[N]>。
场景 3
在 Python 控制台中以交互的方式导入 evaltime_meta.py 模块。
场景 4
在命令行中运行 evaltime_meta.py 模块。
解答和分析如下。
01. 场景3的解答
在 Python 控制台中导入 evaltime_meta.py 模块后得到的输出如示例 21-11 所示。
示例 21-11 场景 3:在 Python 控制台中导入 evaltime_meta 模块
>>> import evaltime_meta <[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
➊ 与场景 1 的关键区别是,创建 ClassFive 时调用了 MetaAleph.__init__ 方法。
➋ 创建 ClassFive 的子类 ClassSix 时也调用了 MetaAleph.__init__ 方法。
Python 解释器计算 ClassFive 类的定义体时没有调用 type 构建具体的类定义体,而是调用 MetaAleph 类。看一下示例 21-12 中定义的 MetaAleph 类,你会发现 __init__ 方法有四个参数。
self
这是要初始化的类对象(例如 ClassFive)。
name、bases、dic
与构建类时传给 type 的参数一样。
示例 21-12 evalsupport.py:定义 MetaAleph 元类,摘自示例 21-7
class MetaAleph(type): print('<[400]> MetaAleph body') def __init__(cls, name, bases, dic): print('<[500]> MetaAleph.__init__') def inner_2(self): print('<[600]> MetaAleph.__init__:inner_2') cls.method_z = inner_2
编写元类时,通常会把 self 参数改成 cls。例如,在上述元类的 __init__ 方法中,把第一个参数命名为 cls 能清楚地表明要构建的实例是类。
__init__ 方法的定义体中定义了 inner_2 函数,然后将其绑定给 cls.method_z。MetaAleph.__init__ 方法签名中的 cls 指代要创建的类(例如 ClassFive)。而 inner_2 函数签名中的 self 最终是指代我们在创建的类的实例(例如 ClassFive 类的实例)。
02. 场景4的解答
在命令行中运行 python3 evaltime_meta.py 命令后得到的输出如示例 21-13 所示。
示例 21-13 场景 4:在 shell 中运行 evaltime_meta.py
$ python3 evaltime.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__ <[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
❶ 装饰器依附到 ClassThree 类上之后,method_y 方法被替换成 inner_1 方法……
❷ 虽然 ClassFour 是 ClassThree 的子类,但是没有依附装饰器的 ClassFour 类却不受影响。
❸ MetaAleph 类的 __init__ 方法把 ClassFive.method_z 方法替换成 inner_2 函数。
❹ ClassFive 的子类 ClassSix 也是一样,method_z 方法被替换成 inner_2 函数。
注意,ClassSix 类没有直接引用 MetaAleph 类,但是却受到了影响,因为它是 ClassFive 的子类,进而也是 MetaAleph 类的实例,所以由 MetaAleph.__init__ 方法初始化。
如果想进一步定制类,可以在元类中实现 __new__ 方法。不过,通常情况下实现 __init__ 方法就够了。
现在,我们可以实践这些理论了。我们将创建一个元类,让描述符以最佳的方式自动创建储存属性的名称。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论