当前位置: 首页 > news >正文

Python 类元编程(导入时和运行时比较)

导入时和运行时比较

为了正确地做元编程,你必须知道 Python 解释器什么时候计算各个代码
块。Python 程序员会区分“导入时”和“运行时”,不过这两个术语没有严
格的定义,而且二者之间存在着灰色地带。在导入时,解释器会从上到
下一次性解析完 .py 模块的源码,然后生成用于执行的字节码。如果句
法有错误,就在此时报告。如果本地的 __pycache__ 文件夹中有最新
的 .pyc 文件,解释器会跳过上述步骤,因为已经有运行所需的字节码
了。

编译肯定是导入时的活动,不过那个时期还会做些其他事,因为 Python
中的语句几乎都是可执行的,也就是说语句可能会运行用户代码,修改
用户程序的状态。尤其是 import 语句,它不只是声明 ,在进程中首
次导入模块时,还会运行所导入模块中的全部顶层代码——以后导入相
同的模块则使用缓存,只做名称绑定。那些顶层代码可以做任何事,包
括通常在“运行时”做的事,例如连接数据库。 因此,“导入时”与“运行
时”之间的界线是模糊的:import 语句可以触发任何“运行时”行为。

在前一段中我写道,导入时会“运行全部顶层代码”,但是“顶层代码”会
经过一些加工。导入模块时,解释器会执行顶层的 def 语句,可是这么
做有什么作用呢?解释器会编译函数的定义体(首次导入模块时),把
函数对象绑定到对应的全局名称上,但是显然解释器不会执行函数的定
义体。通常这意味着解释器在导入时定义顶层函数,但是仅当在运行时
调用函数时才会执行函数的定义体。

对类来说,情况就不同了:在导入时,解释器会执行每个类的定义体,
甚至会执行嵌套类的定义体。执行类定义体的结果是,定义了类的属性
和方法,并构建了类对象。从这个意义上理解,类的定义体属于“顶层
代码”,因为它在导入时运行。

上述说明模糊又抽象,下面通过练习理解各个时期所做的事情。

理解计算时间的练习
假设在 evaltime.py 脚本中导入了 evalsupport.py 模块。这两个模块调用
了几次 print 函数,打印 <[N]> 格式的标记,其中 N 是数字。下述两
个练习的目标是,确定各个调用在何时执行。

那两个模块的代码在示例 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_1return 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')

场景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。

场景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 函数。

与此不同的是,如果想定制整个类层次结构,而不是一次只定制一
个类,使用下一节介绍的元类更高效。

http://www.lryc.cn/news/619753.html

相关文章:

  • Windows也能用!Claude Code硬核指南
  • [激光原理与应用-259]:理论 - 几何光学 - 平面镜的反射、平面透镜的折射、平面镜的反射成像、平面透镜的成像的规律
  • 网刻软件iVentoy软件使用方法
  • @进程管理工具 - Glances工具详细指南
  • Django REST Framework视图
  • Java 大视界 -- Java 大数据机器学习模型在金融资产配置优化与风险收益平衡中的应用(395)
  • 解惑rust中的 Send/Sync(译)
  • 基于Java的Markdown转Word工具(标题、段落、表格、Echarts图等)
  • 18.10 SQuAD数据集实战:5步高效获取与预处理,BERT微调避坑指南
  • 实战多屏Wallpaper壁纸显示及出现黑屏问题bug分析-学员作业
  • HTML <iframe> 标签 如何把html写入iframe标签
  • 版图设计学习2_掌握PDK中的层定义(工艺文档精读)
  • Spring Boot 集成 机器人指令中枢ROS2工业机械臂控制网关
  • 如何在 Spring Boot 中设计和返回树形结构的组织和部门信息
  • 大致计算服务器磁盘使用情况脚本
  • GNhao/GN号,海外SIM号怎么获取的步骤指南
  • npm install 的作用
  • Android实现Glide/Coil样式图/视频加载框架,Kotlin
  • 【KO】Android 网络相关面试题
  • 华为 HCIE 大数据认证中 Linux 命令行的运用及价值
  • 安装Win10怎样跳过欢迎界面
  • 数字货币的去中心化:重构价值交换的底层逻辑​
  • uniapp微信小程序-登录页面验证码的实现(springboot+vue前后端分离)EasyCaptcha验证码 超详细
  • Lombok插件介绍及安装(Eclipse)
  • Python3解释器深度解析与实战教程:从源码到性能优化的全路径探索
  • Day51--图论--99. 岛屿数量(卡码网),100. 岛屿的最大面积(卡码网)
  • 【数据结构】——栈(Stack)的原理与实现
  • 最新Coze(扣子)智能体工作流:用Coze实现「图片生成-视频制作」全自动化,3分钟批量产出爆款内容
  • 自由学习记录(83)
  • 【Unity开发】Unity核心学习(一)