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

Python增强提案

PEP 547 – 使用 -m 选项运行扩展模块

作者:
Marcel Plch <gmarcel.plch at gmail.com>,Petr Viktorin <encukou at gmail.com>
状态:
已延期
类型:
标准跟踪
创建:
2017年5月25日
Python版本:
3.7
历史记录:


目录

延期通知

Cython——本PEP最重要的用例,也是唯一明确的用例——尚未准备好进行多阶段初始化。它在C级静态变量中保留全局状态。请参阅Cython问题1923中的讨论。

在情况发生变化之前,PEP将被延期。

摘要

本PEP建议实现允许使用PEP 489多阶段初始化在__main__命名空间中执行内置模块和扩展模块。

这样,可以使用以下命令运行启用多阶段初始化的模块

$ python3 -m _testmultiphase
This is a test module named __main__.

动机

目前,扩展模块不支持Python源模块的所有功能。具体来说,无法使用Python的-m选项将扩展模块作为脚本运行。

为实现此目标,PEP 489已经完成了技术基础工作,并且在该PEP的“可能的未来扩展”部分中列出了启用-m选项。从技术上讲,此处提出的额外更改相对较小。

基本原理

传统上,通过提供Python包装器来解决扩展模块不支持-m选项的问题。例如,_pickle模块的命令行界面位于纯Python pickle模块中(以及纯Python重新实现)。

这对于标准库模块非常有效,因为使用C API构建命令行界面很麻烦。但是,其他用户可能希望直接创建可执行的扩展模块。

一个重要的用例是Cython,这是一种类似Python的语言,它编译成C扩展模块。Cython是Python的(近乎)超集,这意味着使用Cython编译Python模块通常不会更改模块的功能,从而允许逐步添加Cython特定的功能。本PEP将允许Cython扩展模块在使用-m选项运行时表现与其Python对应模块相同。Cython开发人员认为该功能值得实现(请参阅Cython问题1715)。

背景

Python的-m选项由函数runpy._run_module_as_main处理。

-m指定的模块不会正常导入。相反,它在__main__模块的命名空间中执行,该模块是在解释器初始化的早期创建的。

对于Python源模块,在另一个模块的命名空间中运行不是问题:代码使用设置为现有模块__dict__localsglobals执行。对于扩展模块并非如此,扩展模块的PyInit_*入口点传统上既创建了一个新的模块对象(使用PyModule_Create),又初始化了它。

从Python 3.5开始,扩展模块可以使用PEP 489多阶段初始化。在这种情况下,PyInit_*入口点返回一个PyModuleDef结构:模块如何创建和初始化的描述。扩展可以选择使用Py_mod_create回调来自定义模块对象的创建,或者通过不指定Py_mod_create来选择使用普通模块对象。然后调用另一个回调Py_mod_exec来初始化模块对象,例如通过用方法和类填充它。

提案

多阶段初始化使得在另一个模块的命名空间中执行扩展模块成为可能:如果未指定Py_mod_create回调,则可以将__main__模块传递给Py_mod_exec回调以进行初始化,就像__main__是一个新构造的模块对象一样。

此方案中一个复杂之处是C级模块状态。每个模块都有一个md_state指针,该指针指向在创建扩展模块时分配的内存区域。PyModuleDef指定要分配多少内存。

实现必须注意md_state内存最多只能分配一次。此外,Py_mod_exec回调每个模块只能调用一次。多次初始化模块的影响过于微妙,因此不需要期望扩展作者对其进行推理。md_state指针本身将充当保护:分配内存和调用Py_mod_exec将始终一起完成,并且如果md_state已非NULL,则初始化扩展模块将失败。

由于__main__模块不是作为扩展模块创建的,因此其md_state通常为NULL。在__main__的上下文中初始化扩展模块之前,将根据该模块的PyModuleDef分配其模块状态。

虽然PEP 489旨在使这些更改普遍成为可能,但有必要将扩展模块的模块发现、创建和初始化步骤解耦,以便可以使用另一个模块代替新初始化的模块,并且需要将功能添加到runpyimportlib中。

规范

将添加importlib加载器的一个新的可选方法。此方法将被称为exec_in_module,并将采用两个位置参数:模块规范和一个已存在的模块。模块上已设置的任何与导入相关的属性,例如__spec____name__,都将被忽略。

runpy._run_module_as_main函数将查找此新的加载器方法。如果存在,runpy将执行它,而不是尝试加载并运行模块的Python代码。否则,runpy将像以前一样工作。

ExtensionFileLoader更改

importlib的ExtensionFileLoader将获得exec_in_module的实现,该实现将调用一个新函数_imp.exec_in_module

_imp.exec_in_module将使用现有机制查找并调用扩展模块的PyInit_*函数。

PyInit_*函数可以返回一个完全初始化的模块(单阶段初始化)或一个PyModuleDef(用于PEP 489多阶段初始化)。

在单阶段初始化的情况下,_imp.exec_in_module将引发ImportError

在多阶段初始化的情况下,PyModuleDef和要初始化的模块将传递给一个新函数PyModule_ExecInModule

如果PyModuleDef指定了Py_mod_create槽,或者如果模块已初始化(即其md_state指针不是NULL),则此函数将引发ImportError。否则,该函数将根据PyModuleDef初始化模块。

向后兼容性

本PEP保持向后兼容性。它只添加了新函数和一个新的加载器方法,该方法是为以前不支持将模块作为__main__运行的加载器添加的。

参考实现

本PEP的参考实现可在GitHub上找到。


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

上次修改时间:2023年9月9日17:39:29 GMT