PEP 488 – 废除PYO文件
- 作者:
- Brett Cannon <brett at python.org>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2015年2月20日
- Python 版本:
- 3.5
- 发布历史:
- 2015年3月6日,2015年3月13日,2015年3月20日
摘要
本PEP提议从Python中废除PYO文件的概念。为了继续支持基于优化级别分离字节码文件,本PEP提议在应用优化时,扩展PYC文件名以在字节码存储库目录中包含优化级别。
基本原理
截至目前,字节码文件有两种形式:PYC和PYO。PYC文件是在解释器启动时未指定优化级别(即未指定-O)时生成和读取的字节码文件。PYO文件表示在指定**任何**优化级别(即指定-O**或**-OO)时读取/写入的字节码文件。这意味着虽然PYC文件清楚地标明了它们生成时使用的优化级别——即除了窥孔优化器之外没有其他优化——但对于PYO文件却并非如此。用优化级别和文件扩展名来表达:
- 0:
.pyc - 1 (
-O):.pyo - 2 (
-OO):.pyo
.pyo文件扩展名在级别1和级别2优化中重复使用,这意味着无法明确判断生成字节码文件时使用了哪个优化级别。在读取PYO文件方面,如果用户不小心确保所有PYO文件都使用相同的优化级别生成(通常通过盲目删除所有PYO文件,然后使用compileall模块编译全新的PYO文件[1]),这可能导致解释器在其代码中使用混合优化级别。当人们对Python代码进行解释器原生支持之外的优化时,例如使用astoptimizer项目[2],这个问题会进一步加剧。
在编写PYO文件方面,每次更改要使用的优化级别或不确定上次生成PYO文件时使用了哪个优化级别时都需要删除所有PYO文件,这导致不必要的文件搅动。本PEP提出的更改还允许**所有**优化级别提前为字节码文件预编译,这目前是不可能的,因为.pyo文件扩展名被多个优化级别重用。
至于分发仅字节码的模块,对于代码混淆和较小文件部署的常见用例,同时分发.pyc和.pyo文件是不必要的。这意味着仅字节码的模块将只能从其未优化的.pyc文件名加载。
提案
为了消除PYO文件带来的歧义,本PEP提议废除PYO文件的概念及其附带的.pyo文件扩展名。为了使优化级别明确,并避免在__pycache__目录中不必要地重新生成优化字节码文件,用于生成字节码文件的优化级别将包含在字节码文件名中。当未指定优化级别时,将使用PEP之前的.pyc文件名(即文件名中不指定优化级别)。例如,CPython 3.5中名为foo.py的源文件根据解释器的优化级别(无、-O和-OO)可能具有以下字节码文件:
- 0:
foo.cpython-35.pyc(即不变) - 1:
foo.cpython-35.opt-1.pyc - 2:
foo.cpython-35.opt-2.pyc
目前字节码文件名由importlib.util.cache_from_source()创建,大约使用PEP 3147 [3], [4]定义的以下表达式:
'{name}.{cache_tag}.pyc'.format(name=module_name,
cache_tag=sys.implementation.cache_tag)
本PEP提议,当指定优化级别时,将表达式更改为:
'{name}.{cache_tag}.opt-{optimization}.pyc'.format(
name=module_name,
cache_tag=sys.implementation.cache_tag,
optimization=str(sys.flags.optimize))
选择“opt-”前缀是为了提供与缓存标签的视觉分隔。将优化级别放置在缓存标签之后是为了保留基于模块名和缓存标签的字节码文件名的词典排序顺序,这对于单个解释器来说不会改变。选择“opt-”前缀而不是“o”是为了使其具有一定的自解释性。选择“opt-”前缀而不是“O”是为了避免在优化级别以“0”开头时可能出现的任何混淆。
选择句点而不是连字符作为分隔符,是为了明确区分优化级别不属于缓存标签指定的解释器版本。它还有助于在文件名中使用句点来区分语义上不同的概念。
例如,如果向解释器传递了-OO,则文件名将是importlib.cpython-35.opt-2.pyc,而不是importlib.cpython-35.pyo。
当不应用优化级别时,省略新的opt-标签应该会提高向后兼容性。这也更能理解那些不需要优化级别的Python实现(例如PyPy [10])。
应该注意的是,此更改绝不影响导入性能。由于导入系统已经根据解释器的优化级别查找单个字节码文件,如果不存在则生成新的字节码文件,因此在__pycache__目录中引入更多字节码文件在stat调用方面没有影响。解释器将继续只查找基于优化级别的单个字节码文件,因此不会增加stat调用。
本PEP唯一潜在的负面结果是.pyc文件数量可能增加,从而导致存储使用量增加。但对于存在此问题的平台,sys.dont_write_bytecode存在以关闭字节码生成,从而可以在离线控制。
实施
本PEP的实现可在[11]处获取。
importlib
由于importlib.util.cache_from_source()是公开字节码文件路径的API,并且直接被importlib使用,因此它需要最关键的更改。截至Python 3.4,该函数的签名是
importlib.util.cache_from_source(path, debug_override=None)
本PEP提议将Python 3.5中的签名更改为:
importlib.util.cache_from_source(path, debug_override=None, *, optimization=None)
引入的optimization仅限关键字参数将控制文件名中指定的优化级别。如果参数为None,则假定为解释器的当前优化级别(包括无优化)。optimization的任何给定参数都将传递给str(),并且必须使str.isalnum()为真,否则将引发ValueError(这可以防止文件名中使用无效字符)。如果为optimization传递空字符串,则将抑制优化级别的添加,恢复到此PEP之前的文件名格式。
除了Python自己的两个优化级别之外,预计第三方代码将使用优化名称的哈希值来指定优化级别,例如hashlib.sha256(','.join(['no dead code', 'const folding'])).hexdigest()。虽然这可能导致文件名过长,但假设大多数用户从不查看__pycache__目录的内容,因此这不会成为问题。
debug_override参数将被弃用。False值将等同于optimization=1,而True值将表示optimization=''(None参数将继续表示与optimization相同)。当debug_override被赋予除None之外的值时,将引发弃用警告,但目前没有完全删除该参数的计划(但最迟不晚于Python 4)。
importlib.machinery中与字节码文件后缀相关的各种模块属性将进行更新[7]。DEBUG_BYTECODE_SUFFIXES和OPTIMIZED_BYTECODE_SUFFIXES都将被标记为已弃用,并设置为与BYTECODE_SUFFIXES相同的值(目前不计划移除DEBUG_BYTECODE_SUFFIXES和OPTIMIZED_BYTECODE_SUFFIXES,但最迟不晚于Python 4)。
所有各种查找器和加载器也将根据需要进行更新,但更新前面提到的importlib部分应该就足够了。
标准库的其余部分
由py_compile和compileall函数公开的各种函数将根据需要进行更新,以确保它们遵循新的字节码文件名语义[6],[1]。compileall模块的CLI将不受直接影响(当指定-O时,-b标志将是隐式的,因为它将不再生成.pyo文件)。
兼容性考虑
从Python 3.2开始,任何直接操作字节码文件的代码都需要考虑此更改对其代码的影响(在Python 3.2之前——包括所有Python 2——没有__pycache__,这已经需要分叉字节码文件处理支持)。如果代码将debug_override参数设置为importlib.util.cache_from_source(),那么如果他们想要优化级别为2的字节码文件的路径,则需要小心。否则,只有**不**使用importlib.util.cache_from_source()的代码才需要更新。
至于分发仅字节码模块(即,使用字节码文件而不是源文件)的人员,他们将不得不选择他们希望其字节码文件具有哪个优化级别,因为分发带有.pyc文件的.pyo文件将不再有用。由于人们通常只出于代码混淆目的或较小的分发大小而分发字节码文件,因此只需分发单个.pyc文件实际上对这些用例应该是有益的。而且,由于Python 3.5中字节码文件的魔数已更改以支持PEP 465,因此无需支持现有的.pyo文件[8]。
被拒绝的想法
从CPython完全移除优化级别
一些人建议,与其在 CPython 中容纳各种优化级别,不如完全取消它们。论点是,显著的性能提升将来自通过 JIT 等运行时优化,而不是预执行字节码优化。
本 PEP 驳回了这一想法,因为这忽略了 CPython 现有优化级别对某些人仍然有用的事实。它还假设没有其他 Python 解释器会觉得本 PEP 提出的内容有用。
文件名中优化级别的替代格式
使用“opt-”前缀并将优化级别置于缓存标签和文件扩展名之间并非关键。已考虑的所有选项包括:
importlib.cpython-35.opt-1.pycimportlib.cpython-35.opt1.pycimportlib.cpython-35.o1.pycimportlib.cpython-35.O1.pycimportlib.cpython-35.1.pycimportlib.cpython-35-O1.pycimportlib.O1.cpython-35.pycimportlib.o1.cpython-35.pycimportlib.1.cpython-35.pyc
这些选项最初被拒绝,原因要么是它们会改变字节码文件的排序顺序,要么是与缓存标签可能存在歧义,要么是自解释性不够。进行了一次非正式投票,人们明显更喜欢PEP提议的格式[9]。由于该主题是非技术性的,属于个人选择,因此该问题被认为是已解决的。
将优化级别嵌入到字节码元数据中
有人建议,与其将字节码的优化级别嵌入到文件名中,不如将其包含在文件的元数据中。这意味着每个解释器在任何时候都只有一份字节码。因此,更改优化级别将需要重写字节码,但那时也只有一个文件需要关注。
这已被拒绝,原因是 Python 通常作为根级应用程序安装,因此修改标准库中模块的字节码文件总是可能的。在这种情况下,集成商需要猜测对于任何/所有情况,什么是用户合理的优化级别。通过允许多个优化级别同时存在,它使集成商无需猜测用户想要什么,并允许用户利用他们想要的优化级别。
参考资料
版权
本文档已置于公共领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0488.rst
最后修改: 2025-02-01 08:59:27 GMT