PEP 304 – 控制字节码文件生成
- 作者:
- Skip Montanaro
- 状态:
- 已撤回
- 类型:
- 标准跟踪
- 创建:
- 2003-01-22
- 历史记录:
- 2003-01-27, 2003-01-31, 2005-06-17
历史说明
虽然这个最初的 PEP 被撤回了,但这个功能的一个变体最终在 Python 3.8 中被实现,参见 https://bugs.python.org/issue33499
这个 PEP 中最初提出的几个问题和担忧,在随后的几年中通过其他更改得到了解决
- 引入隔离模式来处理潜在的安全问题
- 切换到
importlib
,一个完全基于导入钩子的导入系统实现 - PEP 3147 中关于字节码缓存布局的更改,使用
__pycache__
子目录,包括source_to_cache(path)
和cache_to_source(path)
API,允许解释器自动处理重定向到单独的缓存目录
摘要
本 PEP 概述了一种控制编译 Python 字节码文件生成和位置的机制。这个想法最初是在一个补丁请求 [1] 中提出的,并发展成为 python-dev 邮件列表上的一个讨论主题 [2]。引入一个环境变量将允许安装 Python 或基于 Python 的第三方包的人员控制是否在安装时生成字节码文件,以及如果生成,应该将它们写入哪里。它还将允许用户控制是否在应用程序运行时生成字节码文件,以及如果生成,应该将它们写入哪里。
提案
在 Python 理解的环境变量中添加一个新的环境变量 PYTHONBYTECODEBASE。
- 如果未定义,Python 字节码将与当前方式完全相同的方式生成。sys.bytecodebase 设置为根目录(在 Unix 和 Mac OSX 上为 /,在 Windows 上为启动(安装??)驱动器的根目录,通常为
C:\
)。 - 如果已定义并且它引用用户具有写入权限的现有目录,则 sys.bytecodebase 设置为该目录,并且字节码文件被写入以该位置为根的目录结构中。
- 如果已定义但为空,则 sys.bytecodebase 设置为 None,并且完全禁止生成字节码文件。
- 如果已定义且以下情况之一为真
- 它不引用目录,
- 它引用目录,但不是用户具有写入权限的目录
则显示警告,sys.bytecodebase 设置为 None,并且完全禁止生成字节码文件。
在启动初始化后,所有运行时引用都指向 sys.bytecodebase,而不是 PYTHONBYTECODEBASE 环境变量。sys.path 未被修改。
从上面可以看出,sys.bytecodebase 只能取两种有效类型的值:None 或引用系统上有效目录的字符串。
在导入期间,此扩展的工作方式如下
- 进行对模块的正常搜索。搜索顺序大致为:动态加载的扩展模块、Python 源文件、Python 字节码文件。此机制仅在找到 Python 源文件时才发挥作用。
- 一旦找到源模块,就会尝试读取同一目录中的字节编译文件。(这与以前相同。)
- 如果找不到字节编译文件,就会尝试从增广目录中读取字节编译文件。
- 如果需要生成字节码,则生成的字节码将尽可能写入增广目录。
请注意,此 PEP 明确地 *不* 关于提供对字节码文件的处理的模块级或目录级控制。
术语表
- “字节码基础”指的是 sys.bytecodebase 的当前设置。
- “增广目录”指的是由字节码基础和源文件的目录名称组成的目录。
- PYTHONBYTECODEBASE 指的是环境变量,必要时将其与“字节码基础”区分开来。
定位字节码文件
当解释器正在搜索模块时,它将像往常一样使用 sys.path。但是,当考虑可能的字节码文件时,可能会进行额外的字节码文件探测。首先,使用包含源文件的 sys.path 中的目录检查字节码文件(当前行为)。如果在那里没有找到有效的字节码文件(要么不存在,要么存在但已过期),并且字节码基础不为 None,则会使用字节码基础适当地作为前缀的 sys.path 中的目录进行第二次探测。
写入字节码文件
当字节码基础不为 None 时,新的字节码文件将被写入相应的增广目录,而不是直接写入 sys.path 中的目录。
定义增广目录
从概念上讲,字节码文件的增广目录是包含源文件的目录,以字节码基础为前缀。在 Unix 环境中,这将是
pcb = os.path.abspath(sys.bytecodebase)
if sourcefile[0] == os.sep: sourcefile = sourcefile[1:]
augdir = os.path.join(pcb, os.path.dirname(sourcefile))
在 Windows 上,它没有单根目录树,包含源文件的目录的驱动器字母将被视为目录组件,在删除尾部的冒号后。因此,增广目录派生为
pcb = os.path.abspath(sys.bytecodebase)
drive, base = os.path.splitdrive(os.path.dirname(sourcefile))
drive = drive[:-1]
if base[0] == "\\": base = base[1:]
augdir = os.path.join(pcb, drive, base)
修复字节码基地的位置
在程序启动期间,PYTHONBYTECODEBASE 环境变量的值将变为绝对值,检查其有效性并添加到 sys 模块中,实际上
pcb = os.path.abspath(os.environ["PYTHONBYTECODEBASE"])
probe = os.path.join(pcb, "foo")
try:
open(probe, "w")
except IOError:
sys.bytecodebase = None
else:
os.unlink(probe)
sys.bytecodebase = pcb
这允许用户将字节码基础指定为相对路径,但不会使其在程序执行期间受到当前工作目录更改的影响。(我想象不出你希望它在程序执行期间四处移动。)
sys.bytecodebase 没有什么特别之处。用户可以在运行时根据需要更改它,但通常不会修改它。
理由
在许多环境中,非 root 用户无法写入包含 Python 源文件的目录。大多数情况下,这不是问题,因为 Python 源代码通常在安装期间进行字节编译。但是,有些情况下字节码文件可能丢失或需要更新。如果包含源文件的目录不可由当前用户写入,则每次运行导入该模块的程序时都会产生性能损失。 [3] 在某些情况下,还可能会生成警告消息。如果目录可写,则两个独立进程几乎同时尝试写入字节码文件可能会导致文件损坏。 [4]
在有 RAM 磁盘可用的环境中,出于性能原因,可能希望将字节码文件写入该磁盘上的目录。类似地,在 Python 源代码位于网络文件系统上的环境中,可能希望将字节码文件缓存到本地磁盘上。
备选方案
到目前为止提出的唯一其他备选方案 [1] 似乎是在解释器中添加 -R 标志以完全禁用写入字节码文件。本提案包含了这一点。添加命令行选项当然可以,但可能还不够,因为解释器的命令行在安装期间(在程序启动早期???)不可用。
问题
- 模块的 __file__ 属性的解释。我认为模块的 __file__ 属性应该反映字节码文件的真实位置。如果人们想要找到模块的源代码,他们应该使用 imp.find_module(module)。
- 安全性 - 如果 root 设置了 PYTHONBYTECODEBASE 会怎样?是的,这会带来安全风险,但 root 用户所做的许多其他事情也会带来安全风险。root 用户应该除了在安装期间之外,最好不要设置 PYTHONBYTECODEBASE。不过,也许可以将这个问题降到最低。当以 root 身份运行时,解释器应该检查 PYTHONBYTECODEBASE 是否引用除 root 以外的任何人都可以写入的目录。如果是,它可以引发异常或警告,并将 sys.bytecodebase 设置为 None。或者,请参见下一项。
- 更多安全性 - 如果 PYTHONBYTECODEBASE 引用一个通用目录(例如 /tmp)会怎样?在这种情况下,也许只有当文件由当前用户或 root 拥有时,才能加载预先存在的字节码文件。(这在 Windows 上重要吗?)
- 本 PEP 与导入钩子的交互尚未考虑。事实上,实现这个想法的最佳方法可能是作为导入钩子。参见 PEP 302。
- 在当前(在 PEP 304 之前)的环境中,在创建相应的字节码文件后删除源文件是安全的,因为它们位于同一个目录中。在当前定义的 PEP 304 中,情况并非如此。增广目录中的字节码文件只有在源文件存在时才会被考虑,因此在查找以“.pyc”结尾的模块文件时永远不会被考虑。我认为这种行为可能需要改变。
示例
在以下示例中,urllib 源代码位于 /usr/lib/python2.3/urllib.py,并且 /usr/lib/python2.3 在 sys.path 中,但不可由当前用户写入。
- 字节码基础为 /tmp。/usr/lib/python2.3/urllib.pyc 存在且有效。当导入 urllib 时,将使用 /usr/lib/python2.3/urllib.pyc 的内容。不会查询增广目录。不会生成其他字节码文件。
- 字节码基础为 /tmp。/usr/lib/python2.3/urllib.pyc 存在,但已过期。当导入 urllib 时,生成的字节码文件将被写入增广目录中的 urllib.pyc,其值为 /tmp/usr/lib/python2.3。根据需要将创建中间目录。
- 字节码基础为 None。没有找到 urllib.pyc 文件。当导入 urllib 时,不会写入字节码文件。
- 字节码基础为 /tmp。没有找到 urllib.pyc 文件。当导入 urllib 时,生成的字节码文件将被写入增广目录,其值为 /tmp/usr/lib/python2.3。根据需要将创建中间目录。
- 在启动时,PYTHONBYTECODEBASE 为 /tmp/foobar,该目录不存在。将发出警告,sys.bytecodebase 设置为 None,并且在程序执行期间不会写入字节码文件,除非 sys.bytecodebase 随后更改为引用有效的可写目录。
- 在启动时,PYTHONBYTECODEBASE 被设置为 /,该目录存在,但当前用户没有写入权限。此时会发出警告,sys.bytecodebase 被设置为 None,并且在程序执行期间不会写入任何字节码文件,除非稍后将 sys.bytecodebase 更改为指向有效的可写入目录。请注意,即使为特定字节码文件构建的增强目录可能对当前用户具有写入权限,但重要的是字节码基础目录本身具有写入权限。
- 在启动时,PYTHONBYTECODEBASE 被设置为空字符串。sys.bytecodebase 被设置为 None。但不会生成任何警告。如果在导入 urllib 时没有找到 urllib.pyc 文件,则不会写入字节码文件。
在以下 Windows 示例中,urllib 源代码位于 C:\PYTHON22\urllib.py
。 C:\PYTHON22
在 sys.path 中,但当前用户没有写入权限。
- 字节码基础被设置为
C:\TEMP
。C:\PYTHON22\urllib.pyc
存在且有效。当导入 urllib 时,将使用C:\PYTHON22\urllib.pyc
的内容。不会使用增强目录。 - 字节码基础被设置为
C:\TEMP
。C:\PYTHON22\urllib.pyc
存在,但已过期。当导入 urllib 时,一个新的字节码文件将被写入到增强目录中,该目录的值为C:\TEMP\C\PYTHON22
。中间目录将根据需要创建。 - 在启动时,PYTHONBYTECODEBASE 被设置为
TEMP
,应用程序启动时的当前工作目录为H:\NET
。因此,潜在的字节码基础为H:\NET\TEMP
。如果该目录存在且当前用户对其具有写入权限,则 sys.bytecodebase 将被设置为该值。否则,将发出警告,并且 sys.bytecodebase 将被设置为 None。 - 字节码基础为
C:\TEMP
。未找到 urllib.pyc 文件。当导入 urllib 时,生成的字节码文件将被写入到增强目录中,该目录的值为C:\TEMP\C\PYTHON22
。中间目录将根据需要创建。
实现
请参阅 Sourceforge 上的补丁。 [6]
参考
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0304.rst
最后修改时间:2023-09-09 17:39:29 GMT