GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||
GeezerVison is showing peps.python.org — PEP 489 – Multi-phase extension module initialization | peps.python.org
PEP 489 – Multi-phase extension module initialization
PEP 489 – Multi-phase extension module initialization
Table of Contents
Important This PEP is a historical document. The up-to-date, canonical documentation can now be found at Initializing C modules. For Python 3.14+, see Defining extension modules and Module definition struct. × See PEP 1 for how to propose changes. AbstractThis PEP proposes a redesign of the way in which built-in and extension modules interact with the import machinery. This was last revised for Python 3.0 in PEP 3121, but did not solve all problems at the time. The goal is to solve import-related problems by bringing extension modules closer to the way Python modules behave; specifically to hook into the ModuleSpec-based loading mechanism introduced in PEP 451. This proposal draws inspiration from Extensions modules are created in a two-step process, fitting better into
the ModuleSpec architecture, with parallels to Extension modules can safely store arbitrary C-level per-module state in the module that is covered by normal garbage collection and supports reloading and sub-interpreters. Extension authors are encouraged to take these issues into account when using the new API. The proposal also allows extension modules with non-ASCII names. Not all problems tackled in PEP 3121 are solved in this proposal.
In particular, problems with run-time module lookup ( MotivationPython modules and extension modules are not being set up in the same way. For Python modules, the module object is created and set up first, then the module code is being executed (PEP 302). A ModuleSpec object (PEP 451) is used to hold information about the module, and passed to the relevant hooks. For extensions (i.e. shared libraries) and built-in modules, the module
init function is executed straight away and does both the creation and
initialization. The initialization function is not passed the ModuleSpec,
or any information it contains, such as the In Py3, modules are also not being added to sys.modules, which means that a
(potentially transitive) re-import of the module will really try to re-import
it and thus run into an infinite loop when it executes the module init function
again. Without access to the fully-qualified module name, it is not trivial to
correctly add the module to sys.modules either.
This is specifically a problem for Cython generated modules, for which it’s
not uncommon that the module init code has the same level of complexity as
that of any ‘regular’ Python module. Also, the lack of Furthermore, the majority of currently existing extension modules has problems with sub-interpreter support and/or interpreter reloading, and, while it is possible with the current infrastructure to support these features, it is neither easy nor efficient. Addressing these issues was the goal of PEP 3121, but many extensions, including some in the standard library, took the least-effort approach to porting to Python 3, leaving these issues unresolved. This PEP keeps backwards compatibility, which should reduce pressure and give extension authors adequate time to consider these issues when porting. The current processCurrently, extension and built-in modules export an initialization function
named “ During its execution, the module init function creates a module object
based on a In the back, the shared library loader keeps a note of the fully qualified module name of the last module that it loaded, and when a module gets created that has a matching name, this global variable is used to determine the fully qualified name of the module object. This is not entirely safe as it relies on the module init function creating its own module object first, but this assumption usually holds in practice. The proposalThe initialization function ( This multi-phase initialization is an additional possibility. Single-phase initialization, the current practice of returning a fully initialized module object, will still be accepted, so existing code will work unchanged, including binary compatibility. The 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; The m_slots member must be either NULL, or point to an array of
To specify a slot, a unique slot ID must be provided. New Python versions may introduce new slot IDs, but slot IDs will never be recycled. Slots may get deprecated, but will continue to be supported throughout Python 3.x. A slot’s value pointer may not be NULL, unless specified otherwise in the slot’s documentation. The following slots are currently available, and described later:
Unknown slot IDs will cause the import to fail with SystemError. When using multi-phase initialization, the m_name field of Before it is returned from PyInit_*, the static PyModuleDef example_def = {...} PyMODINIT_FUNC PyInit_example(void) { return PyModuleDef_Init(&example_def); } The Pseudo-code OverviewHere is an overview of how the modified importers will operate. Details such as logging or handling of errors and invalid states are left out, and C code is presented with a concise Python-like syntax. The framework that calls the importers is explained in PEP 451.
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)
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)
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
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 ....
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) Module Creation PhaseCreation of the module object – that is, the implementation of
The Py_mod_create slotThe PyObject* (*PyModuleCreateFunction)(PyObject *spec, PyModuleDef *def) The function receives a ModuleSpec instance, as defined in PEP 451,
and the This function is not responsible for setting import-related attributes
specified in PEP 451 (such as There is no requirement for the returned object to be an instance of
Note that when this function is called, the module’s entry in Multiple If Post-creation stepsIf the If the Additionally, initial attributes specified in the
Module Execution PhaseModule execution – that is, the implementation of
The execution phase is done on the Execution slots may be specified multiple times, and are processed in the order
they appear in the slots array.
When using the default import machinery, they are processed after
import-related attributes specified in PEP 451
(such as Pre-Execution stepsBefore processing the execution slots, per-module state is allocated for the
module. From this point on, per-module state is accessible through
The Py_mod_exec slotThe entry in this slot must point to a function with the following signature: int (*PyModuleExecFunction)(PyObject* module) It will be called to initialize a module. Usually, this amounts to setting the module’s initial attributes. The “module” argument receives the module object to initialize. The function must return If Legacy InitThe backwards-compatible single-phase initialization continues to be supported.
In this scheme, the Modules that need to work unchanged on older versions of Python should stick to single-phase initialization, because the benefits it brings can’t be back-ported. Here is an example of a module that supports multi-phase initialization, and falls back to single-phase when compiled for an older version of CPython. It is included mainly as an illustration of the changes needed to enable multi-phase 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 } Built-In modulesAny extension module can be used as a built-in module by linking it into
the executable, and including it in the inittab (either at runtime with
To keep this possibility, all changes to extension module loading introduced in this PEP will also apply to built-in modules. The only exception is non-ASCII module names, explained below. Subinterpreters and Interpreter ReloadingExtensions using the new initialization scheme are expected to support
subinterpreters and multiple Functions incompatible with multi-phase initializationThe The Module state and C-level callbacksDue to the unavailability of
Fixing these cases is outside of the scope of this PEP, but will be needed for the new mechanism to be useful to all modules. Proper fixes have been discussed on the import-sig mailing list [5]. As a rule of thumb, modules that rely on New FunctionsA new function and macro implementing the module creation phase will be added.
These are similar to PyObject * PyModule_FromDefAndSpec(PyModuleDef *def, PyObject *spec) PyObject * PyModule_FromDefAndSpec2(PyModuleDef *def, PyObject *spec, int module_api_version) A new function implementing the module execution phase will be added. This allocates per-module state (if not allocated already), and always processes execution slots. The import machinery calls this method when a module is executed, unless the module is being reloaded: PyAPI_FUNC(int) PyModule_ExecDef(PyObject *module, PyModuleDef *def) Another function will be introduced to initialize a PyObject * PyModuleDef_Init(PyModuleDef *); Additionally, two helpers will be added for setting the docstring and methods on a module: int PyModule_SetDocString(PyObject *, const char *) int PyModule_AddFunctions(PyObject *, PyMethodDef *) Export Hook NameAs portable C identifiers are limited to ASCII, module names must be encoded to form the PyInit hook name. For ASCII module names, the import hook is named
For module names containing non-ASCII characters, the import hook is named
In 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 Examples:
For modules with non-ASCII names, single-phase initialization is not supported. In the initial implementation of this PEP, built-in modules with non-ASCII names will not be supported. Module ReloadingReloading an extension module using Due to limitations in shared library loading (both dlopen on POSIX and
Use cases for reloading other than trying out a new version of the module are too rare to require all module authors to keep reloading in mind. If reload-like functionality is needed, authors can export a dedicated function for it. Multiple modules in one libraryTo support multiple Python modules in one shared library, the library can export additional PyInit* symbols besides the one that corresponds to the library’s filename. Note that this mechanism can currently only be used to load extra modules, but not to find them. (This is a limitation of the loader mechanism, which this PEP does not try to modify.) To work around the lack of a suitable finder, code like the following can be used: 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 On platforms that support symbolic links, these may be used to install one library under multiple names, exposing all exported modules to normal import machinery. Testing and initial implementationsFor testing, a new built-in module The The Summary of API Changes and AdditionsNew functions:
New macros:
New types:
New structures:
Other changes:
The internal The undocumented functions Backwards CompatibilityExisting modules will continue to be source- and binary-compatible with new versions of Python. Modules that use multi-phase initialization will not be compatible with versions of Python that do not implement this PEP. The functions All changed loaders ( Internal functions of Python/import.c and Python/importdl.c will be removed.
(Specifically, these are Possible Future ExtensionsThe slots mechanism, inspired by Some extension modules exports many constants; for example PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN", PY_SSL_ERROR_ZERO_RETURN); Converting this to a declarative list, similar to String constants and types can be handled similarly.
(Note that non-default bases for types cannot be portably specified
statically; this case would need a Another possibility is providing a “ ImplementationWork-in-progress implementation is available in a GitHub repository [3]; a patchset is at [4]. Previous ApproachesStefan Behnel’s initial proto-PEP [1]
had a “ Alyssa (Nick) Coghlan proposed “ The original version of this PEP used A further iteration used a “ ReferencesCopyrightThis document has been placed in the public domain. | ||||||||
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |