PEP 273 – 从 Zip 压缩包导入模块
- 作者:
- James C. Ahlstrom <jim at interet.com>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2001-10-11
- Python 版本:
- 2.3
- 发布历史:
- 2001-10-26
摘要
本 PEP 增加了从 zip 压缩包导入 Python 模块 *.py、*.py[co] 和包的能力。如果 os.listdir 可用,则使用相同的代码来加速正常的目录导入。
注意
Zip 导入已添加到 Python 2.3 中,但最终实现采用的方法与本 PEP 中描述的方法不同。2.3 的实现是 SourceForge 补丁 #652586 [1],它增加了 PEP 302 中描述的新导入钩子。
因此,本 PEP 的其余部分仅具有历史意义。
规范
目前,sys.path 是一个字符串形式的目录名列表。如果本 PEP 被实现,sys.path 中的一个项可以是命名 zip 文件的字符串。zip 压缩包可以包含子目录结构以支持包导入。zip 压缩包满足导入的方式与子目录完全相同。
该实现是 Python 核心中的 C 代码,适用于所有受支持的 Python 平台。
zip 压缩包中可以存在任何文件,但只有文件 *.py 和 *.py[co] 可用于导入。动态模块(*.pyd、*.so)的 Zip 导入是不允许的。
正如 sys.path 当前具有默认目录名一样,也将添加一个默认 zip 压缩包名。否则无法从压缩包导入所有 Python 库文件。
子目录等效性
zip 压缩包必须像子目录树一样对待,以便我们可以根据当前和未来的规则支持包导入。所有 zip 数据都取自中央目录,数据必须正确,并且不考虑“脑残”的 zip 文件。
假设 sys.path 包含“/A/B/SubDir”和“/C/D/E/Archive.zip”,并且我们正在尝试从 Q 包中导入 modfoo。那么 import.c 将生成一个路径和扩展名列表,并查找该文件。生成的路径列表不会因 zip 导入而改变。假设 import.c 生成路径“/A/B/SubDir/Q/R/modfoo.pyc”。那么它也将生成路径“/C/D/E/Archive.zip/Q/R/modfoo.pyc”。查找 SubDir 路径与在压缩包中查找“Q/R/modfoo.pyc”完全等效。
假设您压缩了 /A/B/SubDir/* 及其所有子目录。那么您的 zip 文件将像您的子目录一样满足导入。
嗯,不完全是。您无法从 zip 文件中满足动态模块。动态模块的扩展名是 .dll、.pyd 和 .so。它们依赖于操作系统,并且可能除了从文件加载之外无法加载。有可能从 zip 文件中提取动态模块,将其写入普通文件并加载。但这将意味着创建临时文件,并处理所有 dynload_*.c,这可能不是一个好主意。
尝试导入 *.pyc 时,如果它不可用,则将使用 *.pyo。反之亦然,当查找 *.pyo 时。如果 *.pyc 和 *.pyo 都不可用,或者魔法数字无效,那么 *.py 将被编译并用于满足导入,但编译后的文件将不会保存。Python 通常会将其写入与 *.py 相同的目录,但我们肯定不想写入 zip 文件。我们可以写入 zip 压缩包的目录,但这会使其混乱,例如如果是 /usr/bin 则不好。
未能写入编译文件将使 zip 导入非常缓慢,用户可能无法找出问题所在。因此,最好将 *.pyc 和 *.pyo 与 *.py 一起放入压缩包中。
效率
在 zip 压缩包中查找文件的唯一方法是线性搜索。因此,对于 sys.path 中的每个 zip 文件,我们搜索其名称一次,并将名称以及其他相关数据放入静态 Python 字典中。键是 sys.path 中的压缩包名称与压缩包内的文件名(包括任何子目录)连接起来。这正是 import.c 生成的名称,使查找变得容易。
此机制也用于加速目录(非 zip)导入。见下文。
zlib
压缩的 zip 压缩包需要 zlib 进行解压缩。在进行任何其他导入之前,我们尝试导入 zlib。除非 zlib 可用,否则导入压缩文件将失败并显示“缺少 zlib”的消息。
引导
Python 导入 site.py 本身,它导入 os、nt、ntpath、stat 和 UserDict。它还导入 sitecustomize.py,它可能导入更多模块。Zip 导入必须在导入 site.py 之前可用。
正如 sys.path 中有默认目录一样,也必须有一个或多个默认 zip 压缩包。
问题是名称应该是什么。名称应该与 Python 版本相关联,以便 Python 可执行文件即使在同一台机器上有多个 Python 版本时也能正确找到其对应的库。
我们在 sys.path 中添加一个名称。在 Unix 上,目录是 sys.prefix + "/lib",文件名为 "python%s%s.zip" % (sys.version[0], sys.version[2])。因此,对于 Python 2.2 和前缀 /usr/local,路径 /usr/local/lib/python2.2/ 已经在 sys.path 上,并且将添加 /usr/local/lib/python22.zip。在 Windows 上,文件是 python22.dll 的完整路径,其中“dll”替换为“zip”。zip 压缩包名称总是作为 sys.path 中的第二项插入。第一项是 main.py 的目录(感谢 Tim)。
目录导入
用于加速 zip 导入的静态 Python 字典也可以用于加速正常的目录导入。对于 sys.path 中不是 zip 压缩包的每个项,我们调用 os.listdir,并将目录内容添加到字典中。然后,我们不再在双循环中调用 fopen(),而是直接检查字典。这大大加速了导入。如果 os.listdir 不存在,则不使用字典。
基准测试
| 情况 | 原始 2.2a3 | 使用 os.listdir | Zip 未压缩 | Zip 已压缩 |
|---|---|---|---|---|
| 1 | 3.2 2.5 3.2->1.02 | 2.3 2.5 2.3->0.87 | 1.66->0.93 | 1.5->1.07 |
| 2 | 2.8 3.9 3.0->1.32 | 与情况 1 相同。 | ||
| 3 | 5.7 5.7 5.7->5.7 | 2.1 2.1 2.1->1.8 | 1.25->0.99 | 1.19->1.13 |
| 4 | 9.4 9.4 9.3->9.35 | 与情况 3 相同。 |
情况 1:本地驱动器 C:,sys.path 具有其默认值。情况 2:本地驱动器 C:,包含文件的目录位于 sys.path 的末尾。情况 3:网络驱动器,sys.path 具有其默认值。情况 4:网络驱动器,包含文件的目录位于 sys.path 的末尾。
基准测试是在一台 Pentium 4 克隆机上进行的,1.4 GHz,256 MB。该机器运行 Windows 2000,带有一个 Linux/Samba 网络服务器。时间以秒为单位,是导入大约 100 个 Lib 模块所需的时间。情况 2 和 4 将“正确”目录移动到 sys.path 的末尾。“Uncomp”表示未压缩的 zip 压缩包,“Compr”表示已压缩的 zip 压缩包。
初始时间是在系统重新启动后;“->”之后的时间是在重复运行之后。重新启动后从 C: 导入的时间对于“原始”情况来说变化相当大,但更真实。
自定义导入
该逻辑演示了使用默认搜索直到所需的 Python 模块(在本例中为 os)可用时进行导入的能力。这可以用于引导自定义导入器。例如,如果 __init__.py 中存在“importer()”,则可以将其用于导入。“importer()”可以自由导入 os 和其他模块,这些模块将通过默认机制得到满足。本 PEP 没有定义任何自定义导入器,此说明仅供参考。
实施
C 语言实现作为 SourceForge 补丁 492105 提供。已被补丁 652586 和当前 CVS 取代。[2]
一个新版本(由 Paul Moore 为最近的 CVS 更新)是 645650。已被补丁 652586 和当前 CVS 取代。[3]
Just van Rossum 的一个竞争性实现是 652586,它是 PEP 302 最终实现的基础。PEP 273 已使用 PEP 302 的导入钩子实现。[1]
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0273.rst