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.pswp.cn/pingmian/92943.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92943.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[git diff] 对比检查变更 | 提交前复审 | 版本回退

git diff git diff 是 Git 版本控制系统中用于比较文件差异的核心命令&#xff0c;可以显示工作目录、暂存区&#xff08;Index&#xff09;和仓库历史之间的变化。 通过对比不同版本或状态的文件内容&#xff0c;帮助开发者理解代码变更。 比较工作目录与暂存区 运行以下命令查…

【数据可视化-85】海底捞门店数据分析与可视化:Python + pyecharts打造炫酷暗黑主题大屏

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

物联网之小白调试网关设备

小伙伴们&#xff0c;你们好呀&#xff01;我是老寇&#xff01;跟我一起学习调试网关设备 相信搞过物联网的朋友&#xff0c;对网关设备非常熟悉&#xff0c;本人以小白的视角&#xff0c;手把手教你调试网关设备&#xff01; 工作中使用的是Ubuntu操作系统&#xff0c;因此&a…

Node.js特训专栏-实战进阶:22. Docker容器化部署

🔥 欢迎来到 Node.js 实战专栏!在这里,每一行代码都是解锁高性能应用的钥匙,让我们一起开启 Node.js 的奇妙开发之旅! Node.js 特训专栏主页 专栏内容规划详情 我将从Docker容器化部署的基础概念入手,介绍Node.js应用容器化的步骤,包括创建Dockerfile、构建镜像、运行…

eclipse嵌入式编译速度慢

eclipse 嵌入式 编译 速度慢 同一个项目&#xff0c;eclipse编译速度越来越慢&#xff0c;一开始几秒钟编译完&#xff0c;后面要10分钟。只需要将以下两个程序卸载重新安装即可。

编译Android版本可用的高版本iproute2

背景&#xff1a; Android自带的iproute2 太老&#xff0c;很多指令格式不支持 直接基于Android源码&#xff0c;替换源码下iproute2 代码编译新版&#xff0c;报错太多&#xff0c;于是改用Android NDK工具编译 环境&#xff1a; android-ndk-r25c-linux.zip 下载链接&am…

JavaScript的fetch函数的用法

基本语法fetch函数用于发起网络请求&#xff0c;返回一个Promise对象。基本语法如下&#xff1a;fetch(url, options).then(response > response.json()).then(data > console.log(data)).catch(error > console.error(Error:, error));GET请求发起一个简单的GET请求&…

Json和XML文件相互转化

目录 一.XML转Json文件 示例&#xff1a;将XML转换为JSON 依赖准备 Java代码示例 代码详细讲解 二.Json转XML文件 示例&#xff1a;将JSON转换为XML 依赖准备 Java代码示例 代码详细讲解 关键代码解析 将JSON转换为XML 写入文件 示例输入与输出 三.具有相同功能的…

Python科学计算与可视化领域工具TVTK、Mayavi、Mlab、Traits(附视频教程)

概述 TVTK、Mayavi、Mlab 和 Traits 都是 Python 科学计算与可视化领域中紧密相关的工具&#xff0c;它们常被结合使用来处理和展示三维数据。视频教程&#xff1a;https://pan.quark.cn/s/f73e875225ca 1. TVTK TVTK&#xff08;Traits-based Visualization Toolkit&#xff0…

SQL INSERT INTO SELECT 详解

SQL INSERT INTO SELECT 详解 引言 SQL(Structured Query Language)是数据库操作的基础语言,广泛用于各种关系型数据库管理系统中。在SQL中,INSERT INTO SELECT 是一个强大的功能,它允许用户从一个表中选取数据,并直接将这些数据插入到另一个表中。本文将详细讲解 SQL …

python速成学习路线

第一部分&#xff1a;核心基础&#xff08;语法与工具&#xff09; 目标&#xff1a;掌握 Python 的基本语法规则、数据处理方式和开发工具 核心内容&#xff1a; 环境搭建 安装Python 3.x版本&#xff08;推荐3.10&#xff09;配置开发工具&#xff08;如PyCharm、VS Code或…

自然语言处理的实际应用

在这个信息爆炸的时代&#xff0c;我们每天都在与文字、语音打交道 —— 发送消息、查询信息、使用智能助手…… 这些看似平常的互动背后&#xff0c;都离不开一项关键技术的支撑&#xff1a;自然语言处理&#xff08;NLP&#xff09;。作为人工智能的重要分支&#xff0c;NLP …

Docker实战:为项目打造即开即用的宝塔LNMP环境

Docker实战&#xff1a;为项目打造即开即用的宝塔LNMP环境背景一、准备基础镜像二、启动配置容器&#xff08;关键步骤&#xff09;三、容器内环境配置&#xff08;逐步执行&#xff09;1. 基础环境搭建2. 安装Systemd&#xff08;宝塔依赖&#xff09;3. 安装宝塔面板&#xf…

.net\c#web、小程序、安卓开发之基于asp.net家用汽车销售管理系统的设计与实现

.net\c#web、小程序、安卓开发之基于asp.net家用汽车销售管理系统的设计与实现

药房智能盘库系统:基于CV与时间序列预测的库存革命

> 在医疗资源日益紧张的今天,**全国78%的药房仍依赖人工盘库**,平均每100家药房每年因库存问题损失超50万元。当计算机视觉遇见时间序列预测,一场药房库存管理的智能化革命正在悄然发生。 --- ### 一、传统药房库存的三大痛点与破局思路 #### 致命痛点分析 1. **人工…

【互动屏幕】解析双屏联动在数字展厅中的应用与价值

双屏联动 https://www.bmcyzs.com/ 作为现代展厅设计中的重要技术手段&#xff0c;通过两块或多块屏幕的协同工作&#xff0c;实现了信息的动态展示与交互体验的提升。在展厅环境中&#xff0c;双屏联动软件能够将触摸屏与大屏幕无缝连接&#xff0c;使观众通过简单的操作即可控…

clickhouse基础概念及集群部署

一. 简述&#xff1a; ClickHouse 是一款高性能列式存储数据库&#xff0c;专为海量数据的实时分析场景设计。它以极致的查询速度、高效的存储利用率和强大的并行处理能力著称&#xff0c;广泛应用于日志分析、用户行为分析、业务监控等大数据分析领域。1. 核心特性&#xff1a…

低版本 IntelliJ IDEA 使用高版本 JDK 语言特性的问题

现实问题&#xff1a; 目前最新的 IntelliJ IDEA 已经不支持在 Win7 环境上安装了&#xff0c;如果企业内开发环境仍然是 Win7&#xff0c;就会导致很多问题。 比如当前 IDEA 版本为 2023.1&#xff0c;最大支持 JDK17&#xff0c;如何正常使用 JDK21 的新特性呢&#xff1f;比…

3分钟 Spring AI 实现对话功能

1.什么是spring AISpring AI 是 Spring 官方推出的一个基于 Spring 生态的 AI 应用开发框架&#xff0c;旨在简化将人工智能&#xff08;如大语言模型、生成式 AI&#xff09;集成到 Java 应用中的过程。它提供了统一的 API 和工具&#xff0c;让开发者能更轻松地调用 AI 模型2…

CMake笔记:配置(Configure)、生成(Generate)和构建(Build)

以下为AI生成的内容&#xff1a; 一、配置阶段&#xff08;Configure&#xff09; 本质&#xff1a;解析项目逻辑&#xff0c;构建内存模型 触发命令&#xff1a;cmake -S <源码路径> -B <构建路径> 关键操作与输出&#xff1a;操作类型典型案例输出产物变量定义se…