Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python增强提案

PEP 338 – 以脚本方式执行模块

作者:
Alyssa Coghlan <ncoghlan at gmail.com>
状态:
最终
类型:
标准跟踪
创建:
2004年10月16日
Python版本:
2.5
历史记录:
2004年11月8日,2006年2月11日,2006年2月12日,2006年2月18日

目录

摘要

本PEP定义了将任何Python模块作为脚本执行的语义,可以通过-m命令行开关,或通过runpy.run_module(modulename)调用。

Python 2.4中实现的-m开关非常有限。本PEP建议利用PEP 302导入钩子,允许任何提供对其代码对象访问权限的模块被执行。

基本原理

Python 2.4添加了命令行开关-m,允许使用Python模块命名空间查找模块以作为脚本执行。其动机示例是标准库模块,如pdbprofile,而Python 2.4的实现对于这个有限的目的来说是足够的。

许多用户和开发者都要求扩展此功能,以支持运行位于包内部的模块。提供的一个示例是pychecker的pychecker.checker模块。此功能在Python 2.4的实现中被省略,因为其实现复杂得多,并且最合适的策略并不明确。

python-dev上的意见是,最好将扩展推迟到Python 2.5,并通过PEP流程来帮助确保我们做对了。

从那时起,人们还指出,当前版本的-m不支持zipimport或任何其他类型的替代导入行为(如冻结模块)。

将此功能作为Python模块提供比用C编写要容易得多,并且使所有Python程序都能轻松使用此功能,而不是特定于CPython解释器。然后可以重写CPython的命令行开关以利用新模块。

执行其他脚本的脚本(例如profilepdb)还可以选择使用新模块来提供-m样式支持以识别要执行的脚本。

本提案的范围

在Python 2.4中,使用-m找到的模块就像在命令行上提供了其文件名一样执行。本PEP的目标是尽可能接近地使该语句也适用于包内的模块,或通过替代导入机制(如zipimport)访问的模块。

之前的讨论表明,应该注意的是,本PEP**不是**关于更改使Python模块也可用作脚本的习惯用法(参见PEP 299)。该问题被认为与本PEP解决的特定功能正交。

当前行为

在描述新的语义之前,值得介绍一下Python 2.4的现有语义(因为它们目前仅由源代码和命令行帮助定义)。

当在命令行上使用-m时,它会立即终止选项列表(如-c)。该参数被解释为顶级Python模块的名称(即可以在sys.path上找到的模块)。

如果找到该模块,并且其类型为PY_SOURCEPY_COMPILED,则命令行将有效地从python <options> -m <module> <args>重新解释为python <options> <filename> <args>。这包括正确设置sys.argv[0](某些脚本依赖于此 - Python自己的regrtest.py就是一个例子)。

如果未找到该模块,或者其类型不正确,则会打印错误。

建议的语义

建议的语义非常简单:如果使用-m执行模块,则PEP 302导入机制将用于查找该模块并检索其编译后的代码,然后根据顶级模块的语义执行该模块。解释器通过调用一个新的标准库函数runpy.run_module来执行此操作。

这是由于Python的导入机制查找包内模块的方式造成的。包可以在初始化期间修改其自己的__path__变量。此外,路径可能会受到*.pth文件的影响,并且某些包会在sys.metapath上安装自定义加载器。因此,Python可靠地查找模块的唯一方法是导入包含的包,并使用PEP 302导入钩子来访问Python代码。

请注意,查找要执行的模块的过程可能需要导入包含的包。此类包导入对被执行的模块可见的影响是

  • 包含的包将在sys.modules中
  • 包初始化的任何外部影响(例如,已安装的导入钩子、日志记录器、atexit处理程序等)

参考实现

参考实现可在SourceForge上获得([2]),以及库参考的文档([5])。此实现有两个部分。第一个是提议的标准库模块runpy。第二个是对实现-m开关的代码的修改,使其始终委托给runpy.run_module,而不是尝试直接运行模块。委托的形式为

runpy.run_module(sys.argv[0], run_name="__main__", alter_sys=True)

run_modulerunpy在其公共API中公开的唯一函数。

run_module(mod_name[, init_globals][, run_name][, alter_sys])

执行指定模块的代码并返回生成的模块全局字典。模块的代码首先使用标准导入机制找到(有关详细信息,请参阅PEP 302),然后在新的模块命名空间中执行。

可选的字典参数init_globals可用于在执行代码之前预填充全局字典。提供的字典不会被修改。如果提供的字典中定义了以下任何特殊全局变量,则这些定义将被run_module函数覆盖。

特殊全局变量__name____file____loader____builtins__在执行模块代码之前在全局字典中设置。

__name__如果提供了此可选参数,则设置为run_name,否则设置为原始mod_name参数。

__loader__设置为用于检索模块代码的PEP 302模块加载器(此加载器可能是标准导入机制的包装器)。

__file__设置为模块加载器提供的名称。如果加载器不提供文件名信息,则此参数设置为None

__builtins__自动初始化为对__builtin__模块的顶级命名空间的引用。

如果提供了参数alter_sys并且其值为True,则sys.argv[0]将更新为__file__的值,并且sys.modules[__name__]将更新为正在执行的模块的临时模块对象。在此函数返回之前,sys.argv[0]sys.modules[__name__]都将恢复为其原始值。

当作为脚本调用时,runpy模块查找并执行作为第一个参数提供的模块。它通过删除sys.argv[0](它指的是runpy模块本身)来调整sys.argv,然后调用run_module(sys.argv[0], run_name="__main__", alter_sys=True)

导入语句和主模块

2.5b1的发布表明了本PEP与PEP 328之间令人惊讶的(尽管事后看来很明显)交互 - 从主模块中无法使用显式相对导入。这是因为相对导入依赖于__name__来确定当前模块在包层次结构中的位置。在主模块中,__name__的值始终为'__main__',因此显式相对导入将始终失败(因为它们仅适用于包内的模块)。

调查为什么隐式相对导入在直接执行主模块时似乎可以工作,但在使用 -m 执行时却失败,结果表明此类导入实际上始终被视为绝对导入。由于直接执行的工作方式,包含已执行模块的包会被添加到 sys.path 中,因此其同级模块实际上会被作为顶级模块导入。如果在可能直接执行的模块(例如测试模块或实用程序脚本)中使用隐式相对导入,这很容易导致应用程序中出现多个同级模块的副本。

对于 2.5 版本,建议在任何打算用作主模块的模块中始终使用绝对导入。-m 开关在这里提供了一个好处,因为它将当前目录插入到 sys.path 中,而不是包含主模块的目录。这意味着可以使用 -m 从包内部运行模块,只要当前目录包含包的顶级目录即可。即使包未安装在 sys.path 的任何其他位置,绝对导入也能正常工作。如果直接执行模块并使用绝对导入来检索其同级模块,则需要将顶级包目录安装在 sys.path 的某个位置(因为不会自动添加当前目录)。

这是一个示例文件布局

devel/
    pkg/
        __init__.py
        moduleA.py
        moduleB.py
        test/
            __init__.py
            test_A.py
            test_B.py

只要当前目录是 devel,或者 devel 已经存在于 sys.path 中,并且测试模块使用绝对导入(例如 import pkg moduleA 来检索被测模块),PEP 338 允许测试以如下方式运行:

python -m pkg.test.test_A
python -m pkg.test.test_B

当使用 -m 执行主模块时是否应该支持相对导入,这个问题将在 Python 2.6 中重新审视。允许它需要更改 Python 的导入语义或用于指示模块何时为主模块的语义,因此这不是一个可以草率做出的决定。

已解决的问题

一些关键的设计决策影响了 runpy 模块的开发。这些列在下面。

  • 特殊变量 __name____file____loader__ 在模块执行之前设置在模块的全局命名空间中。由于 run_module 会更改这些值,因此它不会修改提供的字典。如果它修改了,那么将 globals() 传递给此函数可能会产生不良的副作用。
  • 有时,填充特殊变量所需的信息根本不可用。与其尝试过于聪明,不如在无法确定相关信息时简单地将这些变量设置为 None
  • 对 alter_sys 参数没有特殊保护。如果文件名信息不可用,这可能会导致 sys.argv[0] 设置为 None
  • 导入锁未被使用,以避免在 alter_sys 设置为 True 时出现的潜在线程问题。相反,建议线程代码简单地避免使用此标志。

备选方案

第一个考虑的替代实现忽略了包的 __path__ 变量,并且只在主包目录中查找。具有此行为的 Python 脚本可以在 execmodule 烹饪书食谱 [3] 的讨论中找到。

execmodule 烹饪书食谱本身是在此 PEP 的早期版本中提出的机制(在 PEP 的作者阅读 PEP 302 之前)。

这两种方法都被拒绝,因为它们不满足 -m 开关的主要目标——允许使用完整的 Python 命名空间来查找要从命令行执行的模块。

此 PEP 的早期版本包含一些关于 exec 如何处理局部字典和来自函数对象的代码的错误假设。这些错误的假设导致了一些不必要的复杂设计,现在已删除 - run_code 共享 exec 的所有特性。

PEP 的早期版本还公开了一个比实现对 -m 开关更新所需的单个 run_module() 函数更广泛的 API。为了简单起见,这些额外的函数已从提议的 API 中删除。

在 SVN 中的最初实现之后,很明显,在执行初始应用程序脚本时保持导入锁是不正确的(例如 python -m test.regrtest test_threadedimport 失败)。因此,run_module 函数仅在实际搜索模块期间保持导入锁,并在执行之前释放它,即使设置了 alter_sys

参考文献


来源:https://github.com/python/peps/blob/main/peps/pep-0338.rst

上次修改:2023-10-11 12:05:51 GMT