- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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.3 导入时和运行时比较
为了正确地做元编程,你必须知道 Python 解释器什么时候计算各个代码块。Python 程序员会区分“导入时”和“运行时”,不过这两个术语没有严格的定义,而且二者之间存在着灰色地带。在导入时,解释器会从上到下一次性解析完 .py 模块的源码,然后生成用于执行的字节码。如果句法有错误,就在此时报告。如果本地的 __pycache__ 文件夹中有最新的 .pyc 文件,解释器会跳过上述步骤,因为已经有运行所需的字节码了。
编译肯定是导入时的活动,不过那个时期还会做些其他事,因为 Python 中的语句几乎都是可执行的,也就是说语句可能会运行用户代码,修改用户程序的状态。尤其是 import 语句,它不只是声明 3,在进程中首次导入模块时,还会运行所导入模块中的全部顶层代码——以后导入相同的模块则使用缓存,只做名称绑定。那些顶层代码可以做任何事,包括通常在“运行时”做的事,例如连接数据库。4 因此,“导入时”与“运行时”之间的界线是模糊的:import 语句可以触发任何“运行时”行为。
3Java 中的 import 语句则只是声明,用于告知编译器需要特定的包。
4我不是说导入模块时应该连接数据库,只是指出来可以做到。
在前一段中我写道,导入时会“运行全部顶层代码”,但是“顶层代码”会经过一些加工。导入模块时,解释器会执行顶层的 def 语句,可是这么做有什么作用呢?解释器会编译函数的定义体(首次导入模块时),把函数对象绑定到对应的全局名称上,但是显然解释器不会执行函数的定义体。通常这意味着解释器在导入时定义顶层函数,但是仅当在运行时调用函数时才会执行函数的定义体。
对类来说,情况就不同了:在导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。执行类定义体的结果是,定义了类的属性和方法,并构建了类对象。从这个意义上理解,类的定义体属于“顶层代码”,因为它在导入时运行。
上述说明模糊又抽象,下面通过练习理解各个时期所做的事情。
理解计算时间的练习
假设在 evaltime.py 脚本中导入了 evalsupport.py 模块。这两个模块调用了几次 print 函数,打印 <[N]> 格式的标记,其中 N 是数字。下述两个练习的目标是,确定各个调用在何时执行。
据我的学生说,这两个练习有助于更好地理解 Python 计算源码的方式。在查看场景 1 的解答之前,请一定要拿出纸和笔,花点时间作答。
那两个模块的代码在示例 21-6 和示例 21-7 中。先别运行代码,拿出纸和笔,按顺序写出下述两个场景输出的标记。
场景 1
在 Python 控制台中以交互的方式导入 evaltime.py 模块:
>> import evaltime
场景 2
在命令行中运行 evaltime.py 模块:
$ python3 evaltime.py
示例 21-6 evaltime.py:按顺序写出输出的序号标记 <[N]>
from evalsupport import deco_alpha print('<[1]> evaltime module start') class ClassOne(): print('<[2]> ClassOne body') def __init__(self): print('<[3]> ClassOne.__init__') def __del__(self): print('<[4]> ClassOne.__del__') def method_x(self): print('<[5]> ClassOne.method_x') class ClassTwo(object): print('<[6]> ClassTwo body') @deco_alpha class ClassThree(): print('<[7]> ClassThree body') def method_y(self): print('<[8]> ClassThree.method_y') class ClassFour(ClassThree): print('<[9]> ClassFour body') def method_y(self): print('<[10]> ClassFour.method_y') if __name__ == '__main__': print('<[11]> ClassOne tests', 30 * '.') one = ClassOne() one.method_x() print('<[12]> ClassThree tests', 30 * '.') three = ClassThree() three.method_y() print('<[13]> ClassFour tests', 30 * '.') four = ClassFour() four.method_y() print('<[14]> evaltime module end')
示例 21-7 evalsupport.py:evaltime.py 导入的模块
print('<[100]> evalsupport module start') def deco_alpha(cls): print('<[200]> deco_alpha') def inner_1(self): print('<[300]> deco_alpha:inner_1') cls.method_y = inner_1 return cls 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 print('<[700]> evalsupport module end')
01. 场景1的解答
在 Python 控制台中导入 evaltime.py 模块后得到的输出如示例 21-8 所示。
示例 21-8 场景 1:在 Python 控制台中导入 evaltime 模块
>>> import evaltime <[100]> evalsupport module start ➊ <[400]> MetaAleph body ➋ <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body ➌ <[6]> ClassTwo body ➍ <[7]> ClassThree body <[200]> deco_alpha ➎ <[9]> ClassFour body <[14]> evaltime module end ➏
❶ evalsupport 模块中的所有顶层代码在导入模块时运行;解释器会编译 deco_alpha 函数,但是不会执行定义体。
❷ MetaAleph 类的定义体运行了。
❸ 每个类的定义体都执行了……
❹ ……包括嵌套的类。
❺ 先计算被装饰的类 ClassThree 的定义体,然后运行装饰器函数。
❻ 在这个场景中,evaltime 模块是导入的,因此不会运行 if __name__ == '__main__': 块。
对于场景 1,要注意以下几点。
(1) 这个场景由简单的 import evaltime 语句触发。
(2) 解释器会执行所导入模块及其依赖(evalsupport)中的每个类定义体。
(3) 解释器先计算类的定义体,然后调用依附在类上的装饰器函数,这是合理的行为,因为必须先构建类对象,装饰器才有类对象可处理。
(4) 在这个场景中,只运行了一个用户定义的函数或方法——deco_alpha 装饰器。
下面来看场景 2。
02. 场景2的解答
运行 python3 evaltime.py 命令后得到的输出如示例 21-9 所示。
示例 21-9 场景 2:在 shell 中运行 evaltime.py
$ python3 evaltime.py <[100]> evalsupport module start <[400]> MetaAleph body <[700]> evalsupport module end <[1]> evaltime module start <[2]> ClassOne body <[6]> ClassTwo body <[7]> ClassThree body <[200]> deco_alpha <[9]> ClassFour body ➊ <[11]> ClassOne tests .............................. <[3]> ClassOne.__init__ ➋ <[5]> ClassOne.method_x <[12]> ClassThree tests .............................. <[300]> deco_alpha:inner_1 ➌ <[13]> ClassFour tests .............................. <[10]> ClassFour.method_y <[14]> evaltime module end <[4]> ClassOne.__del__ ➍
❶ 目前为止,输出与示例 21-8 相同。
❷ 类的标准行为。
❸ deco_alpha 装饰器修改了 ClassThree.method_y 方法,因此调用 three.method_y() 时会运行 inner_1 函数的定义体。
❹ 只有程序结束时,绑定在全局变量 one 上的 ClassOne 实例才会被垃圾回收程序回收。
场景 2 主要想说明的是,类装饰器可能对子类没有影响。在示例 21-6 中,我们把 ClassFour 定义为 ClassThree 的子类。ClassThree 类上依附的 @deco_alpha 装饰器把 method_y 方法替换掉了,但是这对 ClassFour 类根本没有影响。当然,如果 ClassFour.method_y 方法使用 super(...) 调用 ClassThree.method_y 方法,我们便会看到装饰器起作用,执行 inner_1 函数。
与此不同的是,如果想定制整个类层次结构,而不是一次只定制一个类,使用下一节介绍的元类更高效。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论