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

Python 增强提案

PEP 489 – 多阶段扩展模块初始化

作者:
Petr Viktorin <encukou at gmail.com>, Stefan Behnel <stefan_ml at behnel.de>, Alyssa Coghlan <ncoghlan at gmail.com>
BDFL-代表:
Eric Snow <ericsnowcurrently at gmail.com>
讨论地址:
Import-SIG 邮件列表
状态:
最终版
类型:
标准追踪
创建日期:
2013-08-11
Python 版本:
3.5
历史记录:
2013-08-23, 2015-02-20, 2015-04-16, 2015-05-07, 2015-05-18
决议:
Python-Dev 消息

目录

摘要

本 PEP 提案重新设计了内置模块和扩展模块与导入机制交互的方式。此提案最后一次修改是在 Python 3.0 中,通过 PEP 3121 进行的,但当时并未解决所有问题。目标是通过使扩展模块更接近 Python 模块的行为方式来解决与导入相关的 문제점; 特别是,将扩展模块整合到 PEP 451 中引入的基于 ModuleSpec 的加载机制。

该提案借鉴了 PEP 384 中的 PyType_Spec,允许扩展作者仅定义他们需要的功能,并允许将来为扩展模块声明添加功能。

扩展模块以两步方式创建,更好地融入 ModuleSpec 架构,类似于类的 __new__ 和 __init__。

扩展模块可以安全地将任意的 C 级每个模块状态存储在模块中,该状态由正常的垃圾收集覆盖,并支持重新加载和子解释器。鼓励扩展作者在使用新的 API 时考虑这些问题。

该提案还允许使用非 ASCII 名称的扩展模块。

本提案并未解决 PEP 3121 中解决的所有问题。特别是,运行时模块查找(PyState_FindModule)的问题将留待未来的 PEP 解决。

动机

Python 模块和扩展模块的设置方式不同。对于 Python 模块,首先创建和设置模块对象,然后执行模块代码(PEP 302)。一个 ModuleSpec 对象(PEP 451)用于保存有关模块的信息,并传递给相关钩子。

对于扩展(即共享库)和内置模块,模块初始化函数会立即执行,并且同时进行创建和初始化。初始化函数不会传递 ModuleSpec 或其中包含的任何信息,例如 __file__ 或完全限定名称。这阻碍了相对导入和资源加载。

在 Py3 中,模块也不会被添加到 sys.modules 中,这意味着对模块的(可能具有传递性的)重新导入将真正尝试重新导入它,因此在再次执行模块初始化函数时会陷入无限循环。在没有完全限定模块名称的情况下,将模块正确添加到 sys.modules 也不容易。这对于 Cython 生成的模块来说是一个特别的问题,因为这些模块的模块初始化代码的复杂程度通常与任何“常规”Python 模块的代码相同。此外,缺乏 __file__ 和 __name__ 信息阻碍了“__init__.py”模块(即包)的编译,尤其是在模块初始化时使用相对导入的情况下。

此外,大多数现有的扩展模块都存在子解释器支持和/或解释器重新加载问题,虽然使用当前的基础设施可以支持这些功能,但既不简单也不高效。解决这些问题是 PEP 3121 的目标,但许多扩展,包括标准库中的一些扩展,采用了最简单的移植到 Python 3 的方法,导致这些问题没有解决。本 PEP 保持向后兼容性,这应该可以减轻压力,并为扩展作者提供充足的时间来在移植时考虑这些问题。

当前流程

目前,扩展模块和内置模块导出一个名为“PyInit_modulename”的初始化函数,该函数以共享库的文件名命名。此函数由导入机制执行,必须返回一个完全初始化的模块对象。该函数不接收任何参数,因此它无法了解其导入上下文。

在执行期间,模块初始化函数基于一个 PyModuleDef 对象创建一个模块对象。然后它继续通过向模块字典添加属性、创建类型等来初始化它。

在后台,共享库加载器会记录它加载的最后一个模块的完全限定模块名称,当创建一个具有匹配名称的模块时,该全局变量用于确定模块对象的完全限定名称。这并不完全安全,因为它依赖于模块初始化函数首先创建自己的模块对象,但这种假设在实践中通常成立。

提案

初始化函数(PyInit_modulename)将被允许返回一个指向 PyModuleDef 对象的指针。导入机制将负责构建模块对象,在初始化的相关阶段(如下所述)调用 PyModuleDef 中提供的钩子。

这种多阶段初始化是一种额外的可能性。单阶段初始化,即当前返回完全初始化的模块对象的做法,仍将被接受,因此现有代码将保持不变,包括二进制兼容性。

PyModuleDef 结构将被更改为包含一个槽列表,类似于 PEP 384 中的 PyType_Spec 用于类型。为了保持二进制兼容性,并且避免引入新的结构(这将引入额外的支持函数和每个模块存储),PyModuleDef 的当前未使用的 m_reload 指针将被更改为保存这些槽。这些结构定义如下:

typedef struct {
    int slot;
    void *value;
} PyModuleDef_Slot;

typedef struct PyModuleDef {
    PyModuleDef_Base m_base;
    const char* m_name;
    const char* m_doc;
    Py_ssize_t m_size;
    PyMethodDef *m_methods;
    PyModuleDef_Slot *m_slots;  /* changed from `inquiry m_reload;` */
    traverseproc m_traverse;
    inquiry m_clear;
    freefunc m_free;
} PyModuleDef;

m_slots 成员必须为 NULL,或者指向一个 PyModuleDef_Slot 结构数组,以一个 id 设置为 0 的槽结束(即 {0, NULL})。

要指定一个槽,必须提供一个唯一的槽 ID。新的 Python 版本可能会引入新的槽 ID,但槽 ID 永远不会被回收。槽可能会被弃用,但将在整个 Python 3.x 中继续得到支持。

槽的值指针不能为 NULL,除非在槽的文档中另有说明。

以下槽当前可用,并在后面进行描述:

  • Py_mod_create
  • Py_mod_exec

未知的槽 ID 将导致导入失败,并出现 SystemError。

当使用多阶段初始化时,PyModuleDef 的 m_name 字段在导入过程中不会被使用;模块名称将从 ModuleSpec 中获取。

在从 PyInit_* 返回之前,必须使用新添加的 PyModuleDef_Init 函数初始化 PyModuleDef 对象。这将设置对象类型(在某些编译器上无法静态完成)、引用计数和内部簿记数据(m_index)。例如,扩展模块“example”将被导出为:

static PyModuleDef example_def = {...}

PyMODINIT_FUNC
PyInit_example(void)
{
    return PyModuleDef_Init(&example_def);
}

PyModuleDef 对象必须在从它创建的模块的生命周期内可用——通常,它将被静态声明。

伪代码概述

以下是修改后的导入器如何运行的概述。日志记录或错误处理和无效状态处理等细节被省略,C 代码以简洁的类 Python 语法呈现。

调用导入器的框架在 PEP 451 中进行了说明。

importlib/_bootstrap.py

class BuiltinImporter:
    def create_module(self, spec):
        module = _imp.create_builtin(spec)

    def exec_module(self, module):
        _imp.exec_dynamic(module)

    def load_module(self, name):
        # use a backwards compatibility shim
        _load_module_shim(self, name)

importlib/_bootstrap_external.py

class ExtensionFileLoader:
    def create_module(self, spec):
        module = _imp.create_dynamic(spec)

    def exec_module(self, module):
        _imp.exec_dynamic(module)

    def load_module(self, name):
        # use a backwards compatibility shim
        _load_module_shim(self, name)

Python/import.c(_imp 模块)

def create_dynamic(spec):
    name = spec.name
    path = spec.origin

    # Find an already loaded module that used single-phase init.
    # For multi-phase initialization, mod is NULL, so a new module
    # is always created.
    mod = _PyImport_FindExtensionObject(name, name)
    if mod:
        return mod

    return _PyImport_LoadDynamicModuleWithSpec(spec)

def exec_dynamic(module):
    if not isinstance(module, types.ModuleType):
        # non-modules are skipped -- PyModule_GetDef fails on them
        return

    def = PyModule_GetDef(module)
    state = PyModule_GetState(module)
    if state is NULL:
        PyModule_ExecDef(module, def)

def create_builtin(spec):
    name = spec.name

    # Find an already loaded module that used single-phase init.
    # For multi-phase initialization, mod is NULL, so a new module
    # is always created.
    mod = _PyImport_FindExtensionObject(name, name)
    if mod:
        return mod

    for initname, initfunc in PyImport_Inittab:
        if name == initname:
            m = initfunc()
            if isinstance(m, PyModuleDef):
                def = m
                return PyModule_FromDefAndSpec(def, spec)
            else:
                # fall back to single-phase initialization
                module = m
                _PyImport_FixupExtensionObject(module, name, name)
                return module

Python/importdl.c

def _PyImport_LoadDynamicModuleWithSpec(spec):
    path = spec.origin
    package, dot, name = spec.name.rpartition('.')

    # see the "Non-ASCII module names" section for export_hook_name
    hook_name = export_hook_name(name)

    # call platform-specific function for loading exported function
    # from shared library
    exportfunc = _find_shared_funcptr(hook_name, path)

    m = exportfunc()
    if isinstance(m, PyModuleDef):
        def = m
        return PyModule_FromDefAndSpec(def, spec)

    module = m

    # fall back to single-phase initialization
    ....

Objects/moduleobject.c

def PyModule_FromDefAndSpec(def, spec):
    name = spec.name
    create = None
    for slot, value in def.m_slots:
        if slot == Py_mod_create:
            create = value
    if create:
        m = create(spec, def)
    else:
        m = PyModule_New(name)

    if isinstance(m, types.ModuleType):
        m.md_state = None
        m.md_def = def

    if def.m_methods:
        PyModule_AddFunctions(m, def.m_methods)
    if def.m_doc:
        PyModule_SetDocString(m, def.m_doc)

def PyModule_ExecDef(module, def):
    if isinstance(module, types.module_type):
        if module.md_state is NULL:
            # allocate a block of zeroed-out memory
            module.md_state = _alloc(module.md_size)

    if def.m_slots is NULL:
        return

    for slot, value in def.m_slots:
        if slot == Py_mod_exec:
            value(module)

模块创建阶段

模块对象的创建——即 ExecutionLoader.create_module 的实现——受 Py_mod_create 槽控制。

Py_mod_create 槽

Py_mod_create 槽用于支持自定义模块子类。值指针必须指向具有以下签名的函数:

PyObject* (*PyModuleCreateFunction)(PyObject *spec, PyModuleDef *def)

该函数接收一个 ModuleSpec 实例,如 PEP 451 中定义,以及 PyModuleDef 结构。它应该返回一个新的模块对象,或者设置错误并返回 NULL。

此函数不负责在新的模块上设置 PEP 451 中指定的与导入相关的属性(例如 __name____loader__)。

返回的对象没有必须是 types.ModuleType 实例的要求。可以使用任何类型,只要它支持设置和获取属性,包括至少与导入相关的属性。但是,只有 ModuleType 实例支持特定于模块的功能,例如每个模块的状态和执行槽的处理。如果返回的不是 ModuleType 子类,则不能定义任何执行槽;如果有,则会引发 SystemError。

请注意,当调用此函数时,模块在 sys.modules 中的条目尚未填充。尝试再次导入同一个模块(可能是传递性地)可能会导致无限循环。建议扩展作者使 Py_mod_create 保持最小化,特别是不要从它调用用户代码。

不能指定多个 Py_mod_create 槽。如果指定了,导入将失败,并出现 SystemError。

如果未指定 Py_mod_create,导入机制将使用 PyModule_New 创建一个正常的模块对象。名称将从 spec 中获取。

创建后步骤

如果 Py_mod_create 函数返回 types.ModuleType 或子类的实例(或如果不存在 Py_mod_create 槽),导入机制将把 PyModuleDef 与模块关联起来。这也使 PyModuleDef 可供执行阶段、PyModule_GetDef 函数和垃圾收集例程(遍历、清除、释放)访问。

如果 Py_mod_create 函数不返回模块子类,则 m_size 必须为 0,m_traverse、m_clear 和 m_free 必须全部为 NULL。否则,将引发 SystemError。

此外,将 PyModuleDef 中指定的初始属性设置在模块对象上,而不管其类型如何:

  • docstring 将从 m_doc 中设置,如果 m_doc 不为 NULL。
  • 模块的函数将从 m_methods 中初始化,如果有的话。

模块执行阶段

模块执行——即 ExecutionLoader.exec_module 的实现——受“执行槽”控制。本 PEP 仅添加了一个,Py_mod_exec,但在未来可能会添加其他槽。

执行阶段是在与模块对象关联的 PyModuleDef 上完成的。对于不是 PyModule_Type 子类的对象(对于它们,PyModule_GetDef 会失败),执行阶段会被跳过。

执行槽可以多次指定,并且按照它们在槽数组中出现的顺序进行处理。在使用默认导入机制时,它们在设置 PEP 451 中指定的与导入相关的属性(例如 __name____loader__)之后以及模块被添加到 sys.modules 中之后进行处理。

执行前步骤

在处理执行槽之前,会为模块分配每个模块的狀態。从这一点开始,每个模块的狀態可以通过 PyModule_GetState 访问。

Py_mod_exec 槽

此槽中的条目必须指向具有以下签名的函数

int (*PyModuleExecFunction)(PyObject* module)

它将被调用来初始化一个模块。通常,这相当于设置模块的初始属性。“module”参数接收要初始化的模块对象。

函数必须在成功时返回 0,或者在发生错误时设置异常并返回 -1

如果 PyModuleExec 替换了模块在 sys.modules 中的条目,那么新对象将在所有执行槽处理完之后被 importlib 机制使用并返回。这是导入机制本身的一个特性。所有槽都是使用从创建阶段返回的模块进行处理的;在执行阶段不会咨询 sys.modules。(请注意,对于扩展模块,实现 Py_mod_create 通常是使用自定义模块对象更好的解决方案。)

传统初始化

向后兼容的单阶段初始化仍然受支持。在这个方案中,PyInit 函数返回一个完全初始化的模块,而不是一个 PyModuleDef 对象。在这种情况下,PyInit 钩子实现了创建阶段,执行阶段是无操作的。

需要在旧版本的 Python 上保持不变的模块应该坚持使用单阶段初始化,因为它带来的好处无法回溯。以下是一个支持多阶段初始化的模块示例,在为旧版本的 CPython 编译时会回退到单阶段初始化。它主要作为一个说明启用多阶段 init 所需更改的示例。

#include <Python.h>

static int spam_exec(PyObject *module) {
    PyModule_AddStringConstant(module, "food", "spam");
    return 0;
}

#ifdef Py_mod_exec
static PyModuleDef_Slot spam_slots[] = {
    {Py_mod_exec, spam_exec},
    {0, NULL}
};
#endif

static PyModuleDef spam_def = {
    PyModuleDef_HEAD_INIT,                      /* m_base */
    "spam",                                     /* m_name */
    PyDoc_STR("Utilities for cooking spam"),    /* m_doc */
    0,                                          /* m_size */
    NULL,                                       /* m_methods */
#ifdef Py_mod_exec
    spam_slots,                                 /* m_slots */
#else
    NULL,
#endif
    NULL,                                       /* m_traverse */
    NULL,                                       /* m_clear */
    NULL,                                       /* m_free */
};

PyMODINIT_FUNC
PyInit_spam(void) {
#ifdef Py_mod_exec
    return PyModuleDef_Init(&spam_def);
#else
    PyObject *module;
    module = PyModule_Create(&spam_def);
    if (module == NULL) return NULL;
    if (spam_exec(module) != 0) {
        Py_DECREF(module);
        return NULL;
    }
    return module;
#endif
}

内置模块

任何扩展模块都可以通过将其链接到可执行文件并将其包含在 inittab 中(在运行时使用 PyImport_AppendInittab,或在配置时使用 freeze 等工具)来用作内置模块。

为了保持这种可能性,本 PEP 中引入的扩展模块加载的所有更改也将应用于内置模块。唯一的例外是非 ASCII 模块名称,将在下面解释。

子解释器和解释器重新加载

使用新的初始化方案的扩展预计要正确支持子解释器和多个 Py_Initialize/Py_Finalize 周期,避免 Python 文档 [6] 中提到的问题。该机制的设计旨在使这变得容易,但扩展作者仍然需要小心。任何用户定义的函数、方法或实例都不能泄漏到不同的解释器。为了实现这一点,所有模块级状态应保留在模块字典中,或在模块对象的存储中通过 PyModule_GetState 访问。一个简单的经验法则是:不要定义任何静态数据,除了没有可变或用户可设置类属性的内置类型。

与多阶段初始化不兼容的函数

当在具有非 NULL m_slots 指针的 PyModuleDef 结构上使用时,PyModule_Create 函数将失败。该函数没有访问多阶段初始化所需的 ModuleSpec 对象。

PyState_FindModule 函数将返回 NULL,PyState_AddModule 和 PyState_RemoveModule 也将在具有非 NULL m_slots 的模块上失败。PyState 注册被禁用,因为可能从同一个 PyModuleDef 创建多个模块对象。

模块状态和 C 级回调

由于 PyState_FindModule 的不可用,任何需要访问模块级状态的函数(包括在模块级别定义的函数、类或异常)都必须直接或间接地接收对模块对象(或其需要的特定对象)的引用。这在当前的两种情况下很困难

  • 类的 Method,它接收对类的引用,但没有接收对类的模块的引用
  • 具有 C 级回调的库,除非回调可以接收在回调注册时设置的自定义数据

修复这些情况超出了本 PEP 的范围,但对于新机制对所有模块有用来说是必要的。在 import-sig 邮件列表 [5] 上讨论了适当的修复方案。

一般来说,依赖于 PyState_FindModule 的模块目前不适合移植到新机制。

新函数

将添加一个实现模块创建阶段的新函数和宏。它们类似于 PyModule_Create 和 PyModule_Create2,除了它们接受一个额外的 ModuleSpec 参数,并处理具有非 NULL 槽的模块定义。

PyObject * PyModule_FromDefAndSpec(PyModuleDef *def, PyObject *spec)
PyObject * PyModule_FromDefAndSpec2(PyModuleDef *def, PyObject *spec,
                                    int module_api_version)

将添加一个实现模块执行阶段的新函数。它分配每个模块的狀態(如果尚未分配),并始终处理执行槽。导入机制在执行模块时调用此方法,除非正在重新加载模块。

PyAPI_FUNC(int) PyModule_ExecDef(PyObject *module, PyModuleDef *def)

将引入另一个函数来初始化 PyModuleDef 对象。这个幂等函数填充类型、引用计数和模块索引。它返回其参数转换为 PyObject*,因此可以直接从 PyInit 函数返回。

PyObject * PyModuleDef_Init(PyModuleDef *);

此外,将添加两个助手来设置模块的 docstring 和方法。

int PyModule_SetDocString(PyObject *, const char *)
int PyModule_AddFunctions(PyObject *, PyMethodDef *)

导出钩子名称

由于可移植的 C 标识符仅限于 ASCII,因此必须对模块名称进行编码以形成 PyInit 钩子名称。

对于 ASCII 模块名称,导入钩子名为 PyInit_<modulename>,其中 <modulename> 是模块的名称。

对于包含非 ASCII 字符的模块名称,导入钩子名为 PyInitU_<encodedname>,其中名称使用 CPython 的“punycode”编码 (Punycode,后缀为小写字母),并将连字符 (“-”) 替换为下划线 (“_”)。

在 Python 中

def export_hook_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyInit' + suffix

示例

模块名称 Init 钩子名称
spam PyInit_spam
lančmít PyInitU_lanmt_2sa6t
スパム PyInitU_zck5b2b

对于具有非 ASCII 名称的模块,不支持单阶段初始化。

在本 PEP 的初始实现中,不支持具有非 ASCII 名称的内置模块。

模块重新加载

使用 importlib.reload() 重新加载扩展模块将继续没有任何效果,除了重新设置与导入相关的属性。

由于共享库加载的限制(包括 POSIX 上的 dlopen 和 Windows 上的 LoadModuleEx),在磁盘上更改后通常不可能加载修改后的库。

除了尝试使用模块的新版本之外,重新加载的其他用例过于罕见,无法要求所有模块作者都记住重新加载。如果需要类似重新加载的功能,作者可以为其导出专用函数。

一个库中的多个模块

为了在一个共享库中支持多个 Python 模块,库可以导出除与库文件名对应的 PyInit* 符号之外的其他符号。

请注意,此机制目前只能用于加载额外的模块,而不能用于查找它们。(这是加载器机制的限制,本 PEP 不会尝试修改。)为了解决缺少合适的查找器的問題,可以使用以下代码

import importlib.machinery
import importlib.util
loader = importlib.machinery.ExtensionFileLoader(name, path)
spec = importlib.util.spec_from_loader(name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module

在支持符号链接的平台上,可以使用符号链接将一个库安装在多个名称下,并将所有导出的模块公开给正常的导入机制。

测试和初始实现

为了进行测试,将创建一个新的内置模块 _testmultiphase。该库将使用“一个库中的多个模块”中描述的机制导出几个额外的模块。

模块 _testcapi 将保持不变,并将无限期地使用单阶段初始化(或者直到不再支持它)。

模块 arrayxx* 将被转换为使用多阶段初始化,作为初始实现的一部分。

API 更改和添加的摘要

新函数

  • PyModule_FromDefAndSpec(宏)
  • PyModule_FromDefAndSpec2
  • PyModule_ExecDef
  • PyModule_SetDocString
  • PyModule_AddFunctions
  • PyModuleDef_Init

新宏

  • Py_mod_create
  • Py_mod_exec

新类型

  • 将公开 PyModuleDef_Type

新结构

  • PyModuleDef_Slot

其他更改

PyModuleDef.m_reload 更改为 PyModuleDef.m_slots。

BuiltinImporterExtensionFileLoader 现在将实现 create_moduleexec_module

内部模块 _imp 将进行向后不兼容的更改:将添加 create_builtincreate_dynamicexec_dynamic;将删除 init_builtinload_dynamic

未记录的函数 imp.load_dynamicimp.init_builtin 将被向后兼容的垫片替换。

向后兼容性

现有模块将继续与新版本的 Python 具有源代码和二进制兼容性。使用多阶段初始化的模块将与未实现本 PEP 的 Python 版本不兼容。

函数 init_builtinload_dynamic 将从模块 _imp 中删除(但不会从模块 imp 中删除)。

所有更改的加载器(BuiltinImporterExtensionFileLoader)将保持向后兼容;方法 load_module 将被垫片替换。

将删除 Python/import.c 和 Python/importdl.c 的内部函数。(具体来说,这些是 _PyImport_GetDynLoadFunc_PyImport_GetDynLoadWindows_PyImport_LoadDynamicModule。)

未来可能的扩展

槽机制受到 PEP 384 中 PyType_Slot 的启发,允许以后进行扩展。

一些扩展模块导出许多常量;例如,_ssl 有一个很长的调用列表,形式如下

PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
                        PY_SSL_ERROR_ZERO_RETURN);

将其转换为类似于 PyMethodDef 的声明性列表,将减少样板代码,并提供免费的错误检查,而这些错误检查通常是缺失的。

字符串常量和类型可以类似地处理。(请注意,类型的非默认基类不能以可移植的方式静态指定;这种情况需要在添加槽之前运行的 Py_mod_exec 函数。不过,免费的错误检查仍然有益。)

另一个可能性是提供一个“main”函数,该函数将在模块传递给 Python 的 -m 开关时运行。为了使这能够工作,runpy 模块需要修改以利用 PEP 451 中引入的基于 ModuleSpec 的加载。此外,还需要添加一种机制来根据模块最初未定义的槽来设置模块。

实现

正在进行的实现可在 Github 仓库 [3] 中获得;补丁集位于 [4] 中。

先前的方法

Stefan Behnel 的初始 proto-PEP [1] 拥有一个名为“PyInit_modulename”的钩子,它会创建一个模块类,然后调用该类的 __init__ 方法来创建模块。此提案与当时(尚未存在)的 PEP 451 不符,PEP 451 中模块创建和初始化被拆分为不同的步骤。它也不支持将扩展加载到预先存在的模块对象中。

Alyssa (Nick) Coghlan 提出了“Create”和“Exec”钩子,并编写了原型实现 [2]。当时 PEP 451 尚未实现,因此原型没有使用 ModuleSpec。

此 PEP 的最初版本使用了 Create 和 Exec 钩子,并允许将扩展加载到任意预先构建的对象中,使用 Exec 钩子。该提案使扩展模块的初始化更接近 Python 模块的初始化方式,但后来意识到这并非一个重要目标。当前的 PEP 描述了一种更简单的解决方案。

进一步的迭代使用了一个名为“PyModuleExport”的钩子作为 PyInit 的替代方案,其中 PyInit 用于现有方案,而 PyModuleExport 用于多阶段方案。但是,无法根据模块名称确定钩子名称,这使得像 freeze 这样的工具自动生成 PyImport_Inittab 变得复杂。仅保留 PyInit 钩子名称,即使它并不完全适合于导出定义,也带来了更简单的解决方案。

参考文献


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

最后修改时间:2023-10-11 12:05:51 GMT