From 8d8b3665b40b83da5a399e9db70cd76f5fa43273 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Tue, 20 May 2025 08:23:41 +0900 Subject: [PATCH 01/23] Update docs --- Doc/c-api/intro.rst | 4 +- Doc/extending/embedding.rst | 19 +- Doc/extending/extending.rst | 293 +++++++++++++++++----------- Doc/extending/newtypes.rst | 2 +- Doc/extending/newtypes_tutorial.rst | 35 ++-- Doc/includes/newtypes/custom.c | 40 ++-- Doc/includes/newtypes/custom2.c | 42 ++-- Doc/includes/newtypes/custom3.c | 40 ++-- Doc/includes/newtypes/custom4.c | 40 ++-- Doc/includes/newtypes/sublist.c | 46 +++-- 10 files changed, 338 insertions(+), 223 deletions(-) diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index 0c20ad17194eb6..d1db1e10e56791 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -127,7 +127,7 @@ complete listing. item defined in the module file. Example:: static struct PyModuleDef spam_module = { - PyModuleDef_HEAD_INIT, + .m_base =PyModuleDef_HEAD_INIT, .m_name = "spam", ... }; @@ -135,7 +135,7 @@ complete listing. PyMODINIT_FUNC PyInit_spam(void) { - return PyModule_Create(&spam_module); + return PyModuleDef_Init(&spam_module); } diff --git a/Doc/extending/embedding.rst b/Doc/extending/embedding.rst index b777862da79f14..cafb9384a9e1fb 100644 --- a/Doc/extending/embedding.rst +++ b/Doc/extending/embedding.rst @@ -245,21 +245,30 @@ Python extension. For example:: return PyLong_FromLong(numargs); } - static PyMethodDef EmbMethods[] = { + static PyMethodDef emb_module_methods[] = { {"numargs", emb_numargs, METH_VARARGS, "Return the number of arguments received by the process."}, {NULL, NULL, 0, NULL} }; - static PyModuleDef EmbModule = { - PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods, - NULL, NULL, NULL, NULL + static PyModuleDef_Slot emb_module_slots[] = { + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} + }; + + struct PyModuleDef embmodule = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "emb", + .m_size = 0, + .m_methods = emb_module_methods, + .m_slots = emb_module_slots, }; static PyObject* PyInit_emb(void) { - return PyModule_Create(&EmbModule); + return PyModuleDef_Init(&embmodule); } Insert the above code just above the :c:func:`main` function. Also, insert the diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index b0493bed75b151..a4ecaab1dd87ee 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -20,6 +20,11 @@ source file by including the header ``"Python.h"``. The compilation of an extension module depends on its intended use as well as on your system setup; details are given in later chapters. +.. note:: + + The documentation is based on multi-phase initialization (:PEP:`489`) + since Python 3.14. See :file:`Modules/xxmodule.c` for more examples. + .. note:: The C extension interface is specific to CPython, and extension modules do @@ -90,11 +95,10 @@ shortly how it ends up being called):: spam_system(PyObject *self, PyObject *args) { const char *command; - int sts; - - if (!PyArg_ParseTuple(args, "s", &command)) + if (!PyArg_ParseTuple(args, "s", &command)) { return NULL; - sts = system(command); + } + int sts = system(command); return PyLong_FromLong(sts); } @@ -204,30 +208,80 @@ value must be in a particular range or must satisfy other conditions, :c:data:`PyExc_ValueError` is appropriate. You can also define a new exception that is unique to your module. For this, you -usually declare a static object variable at the beginning of your file:: - - static PyObject *SpamError; - -and initialize it in your module's initialization function (:c:func:`!PyInit_spam`) +usually declare an object variable in the per-module state and initialize it +in your module's :c:data:`Py_mod_exec` function (:c:func:`!spam_module_exec`) with an exception object:: - PyMODINIT_FUNC - PyInit_spam(void) + typedef struct { + PyObject *SpamError; + } spam_state; + + static int + spam_module_exec(PyObject *module) { - PyObject *m; + spam_state *state = PyModule_GetState(module); + if (state == NULL) { + return -1; + } - m = PyModule_Create(&spammodule); - if (m == NULL) - return NULL; + state->SpamError = PyErr_NewException("spam.error", NULL, NULL); + if (state->SpamError == NULL) { + return -1; + } + if (PyModule_AddObjectRef(module, "error", state->SpamError) < 0) { + return -1; // followed by spam_module_free() then Py_CLEAR() + } - SpamError = PyErr_NewException("spam.error", NULL, NULL); - if (PyModule_AddObjectRef(m, "error", SpamError) < 0) { - Py_CLEAR(SpamError); - Py_DECREF(m); - return NULL; + return 0; + } + + static int + spam_module_traverse(PyObject *module, visitproc visit, void *arg) { + spam_state *state = PyModule_GetState(module); + if (state == NULL) { + return -1; + } + Py_VISIT(st->SpamError); + return 0; + } + + static int + spam_module_clear(PyObject *module) { + spam_state *state = PyModule_GetState(module); + if (state == NULL) { + return -1; } + Py_CLEAR(st->SpamError); + return 0; + } + + static void + spam_module_free(void *module) + { + (void)spam_module_clear((PyObject *)module); + } - return m; + static PyModuleDef_Slot spam_module_slots[] = { + {Py_mod_exec, spam_module_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} + }; + + static struct PyModuleDef spammodule = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "spam", + .m_size = sizeof(spam_state), // size of per-module state + .m_slots = spam_module_slots, + .m_traverse = spam_module_traverse, + .m_clear = spam_module_clear, + .m_free = spam_module_free, + }; + + PyMODINIT_FUNC + PyInit_spam(void) + { + return PyModuleDef_Init(&spammodule); } Note that the Python name for the exception object is :exc:`!spam.error`. The @@ -252,13 +306,15 @@ call to :c:func:`PyErr_SetString` as shown below:: spam_system(PyObject *self, PyObject *args) { const char *command; - int sts; - - if (!PyArg_ParseTuple(args, "s", &command)) + if (!PyArg_ParseTuple(args, "s", &command)) { return NULL; - sts = system(command); + } + int sts = system(command); if (sts < 0) { - PyErr_SetString(SpamError, "System command failed"); + spam_state *state = PyModule_GetState(self); + if (state != NULL) { + PyErr_SetString(state->SpamError, "System command failed"); + } return NULL; } return PyLong_FromLong(sts); @@ -273,8 +329,9 @@ Back to the Example Going back to our example function, you should now be able to understand this statement:: - if (!PyArg_ParseTuple(args, "s", &command)) + if (!PyArg_ParseTuple(args, "s", &command)) { return NULL; + } It returns ``NULL`` (the error indicator for functions returning object pointers) if an error is detected in the argument list, relying on the exception set by @@ -287,7 +344,7 @@ the variable :c:data:`!command` should properly be declared as ``const char The next statement is a call to the Unix function :c:func:`system`, passing it the string we just got from :c:func:`PyArg_ParseTuple`:: - sts = system(command); + int sts = system(command); Our :func:`!spam.system` function must return the value of :c:data:`!sts` as a Python object. This is done using the function :c:func:`PyLong_FromLong`. :: @@ -344,12 +401,9 @@ function. The method table must be referenced in the module definition structure:: static struct PyModuleDef spammodule = { - PyModuleDef_HEAD_INIT, - "spam", /* name of module */ - spam_doc, /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module, - or -1 if the module keeps state in global variables. */ - SpamMethods + ... + .m_methods = SpamMethods, + ... }; This structure, in turn, must be passed to the interpreter in the module's @@ -360,23 +414,17 @@ only non-\ ``static`` item defined in the module file:: PyMODINIT_FUNC PyInit_spam(void) { - return PyModule_Create(&spammodule); + return PyModuleDef_Init(&spammodule); } Note that :c:macro:`PyMODINIT_FUNC` declares the function as ``PyObject *`` return type, declares any special linkage declarations required by the platform, and for C++ declares the function as ``extern "C"``. -When the Python program imports module :mod:`!spam` for the first time, -:c:func:`!PyInit_spam` is called. (See below for comments about embedding Python.) -It calls :c:func:`PyModule_Create`, which returns a module object, and -inserts built-in function objects into the newly created module based upon the -table (an array of :c:type:`PyMethodDef` structures) found in the module definition. -:c:func:`PyModule_Create` returns a pointer to the module object -that it creates. It may abort with a fatal error for -certain errors, or return ``NULL`` if the module could not be initialized -satisfactorily. The init function must return the module object to its caller, -so that it then gets inserted into ``sys.modules``. +:c:func:`!PyInit_spam` is called when each interpreter imports its module +:mod:`!spam` for the first time. (See below for comments about embedding Python.) +A pointer to the module definition must be returned via :c:func:`PyModuleDef_Init`, +so that the import machinery can create the module and store it in ``sys.modules``. When embedding Python, the :c:func:`!PyInit_spam` function is not called automatically unless there's an entry in the :c:data:`PyImport_Inittab` table. @@ -431,25 +479,6 @@ optionally followed by an import of the module:: Py_ExitStatusException(status); } -.. note:: - - Removing entries from ``sys.modules`` or importing compiled modules into - multiple interpreters within a process (or following a :c:func:`fork` without an - intervening :c:func:`exec`) can create problems for some extension modules. - Extension module authors should exercise caution when initializing internal data - structures. - -A more substantial example module is included in the Python source distribution -as :file:`Modules/xxmodule.c`. This file may be used as a template or simply -read as an example. - -.. note:: - - Unlike our ``spam`` example, ``xxmodule`` uses *multi-phase initialization* - (new in Python 3.5), where a PyModuleDef structure is returned from - ``PyInit_spam``, and creation of the module is left to the import machinery. - For details on multi-phase initialization, see :PEP:`489`. - .. _compilation: @@ -790,18 +819,24 @@ Philbrick (philbrick@hks.com):: {NULL, NULL, 0, NULL} /* sentinel */ }; + static PyModuleDef_Slot keywdarg_slots[] = { + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} + }; + static struct PyModuleDef keywdargmodule = { - PyModuleDef_HEAD_INIT, - "keywdarg", - NULL, - -1, - keywdarg_methods + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "keywdarg", + .m_size = 0, + .m_methods = keywdarg_methods, + .m_slots = keywdarg_slots, }; PyMODINIT_FUNC PyInit_keywdarg(void) { - return PyModule_Create(&keywdargmodule); + return PyModuleDef_Init(&keywdargmodule); } @@ -1241,11 +1276,10 @@ The function :c:func:`!spam_system` is modified in a trivial way:: spam_system(PyObject *self, PyObject *args) { const char *command; - int sts; - - if (!PyArg_ParseTuple(args, "s", &command)) + if (!PyArg_ParseTuple(args, "s", &command)) { return NULL; - sts = PySpam_System(command); + } + int sts = PySpam_System(command); return PyLong_FromLong(sts); } @@ -1262,33 +1296,50 @@ The ``#define`` is used to tell the header file that it is being included in the exporting module, not a client module. Finally, the module's initialization function must take care of initializing the C API pointer array:: - PyMODINIT_FUNC - PyInit_spam(void) - { - PyObject *m; - static void *PySpam_API[PySpam_API_pointers]; - PyObject *c_api_object; + typedef struct { + void *PySpam_API[PySpam_API_pointers]; + } spam_state; - m = PyModule_Create(&spammodule); - if (m == NULL) - return NULL; + static int + spam_module_exec(PyObject *module) + { + spam_state *state = PyModule_GetState(module); + if (state == NULL) { + return -1; + } /* Initialize the C API pointer array */ - PySpam_API[PySpam_System_NUM] = (void *)PySpam_System; + state->PySpam_API[PySpam_System_NUM] = PySpam_System; /* Create a Capsule containing the API pointer array's address */ - c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL); - - if (PyModule_Add(m, "_C_API", c_api_object) < 0) { - Py_DECREF(m); - return NULL; + PyObject *capi = PyCapsule_New(state->PySpam_API, "spam._C_API", NULL); + if (PyModule_Add(module, "_C_API", capi) < 0) { + Py_CLEAR(capi); + return -1; } - return m; + return 0; } -Note that ``PySpam_API`` is declared ``static``; otherwise the pointer -array would disappear when :c:func:`!PyInit_spam` terminates! + static PyModuleDef_Slot spam_module_slots[] = { + {Py_mod_exec, spam_module_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} + }; + + static struct PyModuleDef spammodule = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "spam", + .m_size = sizeof(spam_state), + .m_slots = spam_module_slots, + }; + + PyMODINIT_FUNC + PyInit_spam(void) + { + return PyModuleDef_Init(&spammodule); + } The bulk of the work is in the header file :file:`spammodule.h`, which looks like this:: @@ -1318,21 +1369,9 @@ like this:: #else /* This section is used in modules that use spammodule's API */ - static void **PySpam_API; - #define PySpam_System \ (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM]) - /* Return -1 on error, 0 on success. - * PyCapsule_Import will set an exception if there's an error. - */ - static int - import_spam(void) - { - PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0); - return (PySpam_API != NULL) ? 0 : -1; - } - #endif #ifdef __cplusplus @@ -1341,22 +1380,44 @@ like this:: #endif /* !defined(Py_SPAMMODULE_H) */ -All that a client module must do in order to have access to the function -:c:func:`!PySpam_System` is to call the function (or rather macro) -:c:func:`!import_spam` in its initialization function:: + + typedef struct { + void **PySpam_API; + } client_state; + + static int + client_module_exec(PyObject *module) + { + client_state *state = PyModule_GetState(module); + if (state == NULL) { + return -1; + } + state->PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0); + if (state->PySpam_API == NULL) { + return -1; + } + /* additional initialization can happen here */ + return 0; + } + + static PyModuleDef_Slot client_module_slots[] = { + {Py_mod_exec, client_module_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} + }; + + static struct PyModuleDef clientmodule = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "client", + .m_size = sizeof(client_state), + .m_slots = client_module_slots, + }; PyMODINIT_FUNC PyInit_client(void) { - PyObject *m; - - m = PyModule_Create(&clientmodule); - if (m == NULL) - return NULL; - if (import_spam() < 0) - return NULL; - /* additional initialization can happen here */ - return m; + return PyModuleDef_Init(&clientmodule); } The main disadvantage of this approach is that the file :file:`spammodule.h` is diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst index e3612f3a1875ca..2e3a0dbdd3c517 100644 --- a/Doc/extending/newtypes.rst +++ b/Doc/extending/newtypes.rst @@ -563,7 +563,7 @@ be left as zero. Concretely, here is how the statically declared type object would look:: static PyTypeObject TrivialType = { - PyVarObject_HEAD_INIT(NULL, 0) + .ob_base = PyVarObject_HEAD_INIT(NULL, 0) /* ... other members omitted for brevity ... */ .tp_flags = Py_TPFLAGS_MANAGED_WEAKREF | ..., }; diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index 3fc91841416d71..a1a55e79b8599a 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -171,18 +171,18 @@ implementation provided by the API function :c:func:`PyType_GenericNew`. :: .tp_new = PyType_GenericNew, Everything else in the file should be familiar, except for some code in -:c:func:`!PyInit_custom`:: +:c:func:`!custom_module_exec`:: - if (PyType_Ready(&CustomType) < 0) - return; + if (PyType_Ready(&CustomType) < 0) { + return -1; + } This initializes the :class:`!Custom` type, filling in a number of members to the appropriate default values, including :c:member:`~PyObject.ob_type` that we initially set to ``NULL``. :: - if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { - Py_DECREF(m); - return NULL; + if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + return -1; } This adds the type to the module dictionary. This allows us to create @@ -875,27 +875,22 @@ but let the base class handle it by calling its own :c:member:`~PyTypeObject.tp_ The :c:type:`PyTypeObject` struct supports a :c:member:`~PyTypeObject.tp_base` specifying the type's concrete base class. Due to cross-platform compiler issues, you can't fill that field directly with a reference to -:c:type:`PyList_Type`; it should be done later in the module initialization +:c:type:`PyList_Type`; it should be done in the :c:data:`Py_mod_exec` function:: - PyMODINIT_FUNC - PyInit_sublist(void) + static int + sublist_module_exec(PyObject *mod) { - PyObject* m; SubListType.tp_base = &PyList_Type; - if (PyType_Ready(&SubListType) < 0) - return NULL; - - m = PyModule_Create(&sublistmodule); - if (m == NULL) - return NULL; + if (PyType_Ready(&SubListType) < 0) { + return -1; + } - if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) { - Py_DECREF(m); - return NULL; + if (PyModule_AddObjectRef(mod, "SubList", (PyObject *)&SubListType) < 0) { + return -1; } - return m; + return 0; } Before calling :c:func:`PyType_Ready`, the type structure must have the diff --git a/Doc/includes/newtypes/custom.c b/Doc/includes/newtypes/custom.c index 5253f879360210..e67e57420c8155 100644 --- a/Doc/includes/newtypes/custom.c +++ b/Doc/includes/newtypes/custom.c @@ -16,28 +16,38 @@ static PyTypeObject CustomType = { .tp_new = PyType_GenericNew, }; +static int +custom_module_exec(PyObject *mod) +{ + if (PyType_Ready(&CustomType) < 0) { + return -1; + } + + if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + return -1; + } + + return 0; +} + +static PyModuleDef_Slot custom_module_slots[] = { + {Py_mod_exec, custom_module_exec}, + // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} +}; + static PyModuleDef custommodule = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "custom", .m_doc = "Example module that creates an extension type.", - .m_size = -1, + .m_size = 0, + .m_slots = custom_module_slots, }; PyMODINIT_FUNC PyInit_custom(void) { - PyObject *m; - if (PyType_Ready(&CustomType) < 0) - return NULL; - - m = PyModule_Create(&custommodule); - if (m == NULL) - return NULL; - - if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { - Py_DECREF(m); - return NULL; - } - - return m; + return PyModuleDef_Init(&custommodule); } diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c index a87917583ca495..d9b14f27e934cf 100644 --- a/Doc/includes/newtypes/custom2.c +++ b/Doc/includes/newtypes/custom2.c @@ -106,28 +106,38 @@ static PyTypeObject CustomType = { .tp_methods = Custom_methods, }; +static int +custom_module_exec(PyObject *mod) +{ + if (PyType_Ready(&CustomType) < 0) { + return -1; + } + + if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + return -1; + } + + return 0; +} + +static PyModuleDef_Slot custom_module_slots[] = { + {Py_mod_exec, custom_module_exec}, + // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} +}; + static PyModuleDef custommodule = { - .m_base =PyModuleDef_HEAD_INIT, + .m_base = PyModuleDef_HEAD_INIT, .m_name = "custom2", .m_doc = "Example module that creates an extension type.", - .m_size = -1, + .m_size = 0, + .m_slots = custom_module_slots, }; PyMODINIT_FUNC PyInit_custom2(void) { - PyObject *m; - if (PyType_Ready(&CustomType) < 0) - return NULL; - - m = PyModule_Create(&custommodule); - if (m == NULL) - return NULL; - - if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { - Py_DECREF(m); - return NULL; - } - - return m; + return PyModuleDef_Init(&custommodule); } diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c index 854034d4066d20..6f6bf8ad260045 100644 --- a/Doc/includes/newtypes/custom3.c +++ b/Doc/includes/newtypes/custom3.c @@ -151,28 +151,38 @@ static PyTypeObject CustomType = { .tp_getset = Custom_getsetters, }; +static int +custom_module_exec(PyObject *mod) +{ + if (PyType_Ready(&CustomType) < 0) { + return -1; + } + + if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + return -1; + } + + return 0; +} + +static PyModuleDef_Slot custom_module_slots[] = { + {Py_mod_exec, custom_module_exec}, + // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} +}; + static PyModuleDef custommodule = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "custom3", .m_doc = "Example module that creates an extension type.", - .m_size = -1, + .m_size = 0, + .m_slots = custom_module_slots, }; PyMODINIT_FUNC PyInit_custom3(void) { - PyObject *m; - if (PyType_Ready(&CustomType) < 0) - return NULL; - - m = PyModule_Create(&custommodule); - if (m == NULL) - return NULL; - - if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { - Py_DECREF(m); - return NULL; - } - - return m; + return PyModuleDef_Init(&custommodule); } diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c index a0a1eeb289190b..afc87c8bbab157 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -170,28 +170,38 @@ static PyTypeObject CustomType = { .tp_getset = Custom_getsetters, }; +static int +custom_module_exec(PyObject *mod) +{ + if (PyType_Ready(&CustomType) < 0) { + return -1; + } + + if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + return -1; + } + + return 0; +} + +static PyModuleDef_Slot custom_module_slots[] = { + {Py_mod_exec, custom_module_exec}, + // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} +}; + static PyModuleDef custommodule = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "custom4", .m_doc = "Example module that creates an extension type.", - .m_size = -1, + .m_size = 0, + .m_slots = custom_module_slots, }; PyMODINIT_FUNC PyInit_custom4(void) { - PyObject *m; - if (PyType_Ready(&CustomType) < 0) - return NULL; - - m = PyModule_Create(&custommodule); - if (m == NULL) - return NULL; - - if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { - Py_DECREF(m); - return NULL; - } - - return m; + return PyModuleDef_Init(&custommodule); } diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c index 00664f3454156f..e3f792fa7ad921 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -31,7 +31,7 @@ SubList_init(PyObject *op, PyObject *args, PyObject *kwds) } static PyTypeObject SubListType = { - PyVarObject_HEAD_INIT(NULL, 0) + .ob_base = PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "sublist.SubList", .tp_doc = PyDoc_STR("SubList objects"), .tp_basicsize = sizeof(SubListObject), @@ -41,29 +41,39 @@ static PyTypeObject SubListType = { .tp_methods = SubList_methods, }; +static int +sublist_module_exec(PyObject *mod) +{ + SubListType.tp_base = &PyList_Type; + if (PyType_Ready(&SubListType) < 0) { + return -1; + } + + if (PyModule_AddObjectRef(mod, "SubList", (PyObject *)&SubListType) < 0) { + return -1; + } + + return 0; +} + +static PyModuleDef_Slot sublist_module_slots[] = { + {Py_mod_exec, sublist_module_exec}, + // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} +}; + static PyModuleDef sublistmodule = { - PyModuleDef_HEAD_INIT, + .m_base = PyModuleDef_HEAD_INIT, .m_name = "sublist", .m_doc = "Example module that creates an extension type.", - .m_size = -1, + .m_size = 0, + .m_slots = sublist_module_slots, }; PyMODINIT_FUNC PyInit_sublist(void) { - PyObject *m; - SubListType.tp_base = &PyList_Type; - if (PyType_Ready(&SubListType) < 0) - return NULL; - - m = PyModule_Create(&sublistmodule); - if (m == NULL) - return NULL; - - if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) { - Py_DECREF(m); - return NULL; - } - - return m; + return PyModuleDef_Init(&sublistmodule); } From 88c5f3fade658fdc98b5877f5f28cbb93ab1cfd8 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 08:43:29 +0900 Subject: [PATCH 02/23] Don't change an unrelated example --- Doc/extending/newtypes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst index 2e3a0dbdd3c517..e3612f3a1875ca 100644 --- a/Doc/extending/newtypes.rst +++ b/Doc/extending/newtypes.rst @@ -563,7 +563,7 @@ be left as zero. Concretely, here is how the statically declared type object would look:: static PyTypeObject TrivialType = { - .ob_base = PyVarObject_HEAD_INIT(NULL, 0) + PyVarObject_HEAD_INIT(NULL, 0) /* ... other members omitted for brevity ... */ .tp_flags = Py_TPFLAGS_MANAGED_WEAKREF | ..., }; From a4ade5ce3ad8f631bd64d0b9075bcfbe66ba5c17 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 18:43:33 +0900 Subject: [PATCH 03/23] Apply suggestions from code review Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/extending/extending.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index a4ecaab1dd87ee..c2241e2ffcad7a 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -208,8 +208,8 @@ value must be in a particular range or must satisfy other conditions, :c:data:`PyExc_ValueError` is appropriate. You can also define a new exception that is unique to your module. For this, you -usually declare an object variable in the per-module state and initialize it -in your module's :c:data:`Py_mod_exec` function (:c:func:`!spam_module_exec`) +usually declare an object variable in the module's state and initialize it +in the module's :c:data:`Py_mod_exec` function (:c:func:`!spam_module_exec`) with an exception object:: typedef struct { @@ -228,7 +228,7 @@ with an exception object:: if (state->SpamError == NULL) { return -1; } - if (PyModule_AddObjectRef(module, "error", state->SpamError) < 0) { + if (PyModule_AddObjectRef(module, "SpamError", state->SpamError) < 0) { return -1; // followed by spam_module_free() then Py_CLEAR() } @@ -268,7 +268,7 @@ with an exception object:: {0, NULL} }; - static struct PyModuleDef spammodule = { + static struct PyModuleDef spam_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", .m_size = sizeof(spam_state), // size of per-module state @@ -281,7 +281,7 @@ with an exception object:: PyMODINIT_FUNC PyInit_spam(void) { - return PyModuleDef_Init(&spammodule); + return PyModuleDef_Init(&spam_module); } Note that the Python name for the exception object is :exc:`!spam.error`. The @@ -402,7 +402,7 @@ The method table must be referenced in the module definition structure:: static struct PyModuleDef spammodule = { ... - .m_methods = SpamMethods, + .m_methods = spam_methods, ... }; @@ -414,7 +414,7 @@ only non-\ ``static`` item defined in the module file:: PyMODINIT_FUNC PyInit_spam(void) { - return PyModuleDef_Init(&spammodule); + return PyModuleDef_Init(&spam_module); } Note that :c:macro:`PyMODINIT_FUNC` declares the function as ``PyObject *`` return type, @@ -1328,7 +1328,7 @@ function must take care of initializing the C API pointer array:: {0, NULL} }; - static struct PyModuleDef spammodule = { + static struct PyModuleDef spam_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", .m_size = sizeof(spam_state), @@ -1338,7 +1338,7 @@ function must take care of initializing the C API pointer array:: PyMODINIT_FUNC PyInit_spam(void) { - return PyModuleDef_Init(&spammodule); + return PyModuleDef_Init(&spam_module); } The bulk of the work is in the header file :file:`spammodule.h`, which looks @@ -1407,7 +1407,7 @@ like this:: {0, NULL} }; - static struct PyModuleDef clientmodule = { + static struct PyModuleDef client_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "client", .m_size = sizeof(client_state), @@ -1417,7 +1417,7 @@ like this:: PyMODINIT_FUNC PyInit_client(void) { - return PyModuleDef_Init(&clientmodule); + return PyModuleDef_Init(&client_module); } The main disadvantage of this approach is that the file :file:`spammodule.h` is From d6c42e2397dd7ddabc4b61baa2c37569a96e1452 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 19:12:44 +0900 Subject: [PATCH 04/23] ditto --- Doc/c-api/intro.rst | 4 +++- Doc/extending/extending.rst | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index d1db1e10e56791..13927137b91c7d 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -127,8 +127,10 @@ complete listing. item defined in the module file. Example:: static struct PyModuleDef spam_module = { - .m_base =PyModuleDef_HEAD_INIT, + .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", + .m_size = 0, + .m_slots = spam_module_slots, ... }; diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index c2241e2ffcad7a..738825c7e2d4ef 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -363,7 +363,7 @@ macro):: return Py_None; :c:data:`Py_None` is the C name for the special Python object ``None``. It is a -genuine Python object rather than a ``NULL`` pointer, which means "error" in most +genuine Python object rather than a ``NULL`` pointer, which means "SpamError" in most contexts, as we have seen. @@ -479,6 +479,15 @@ optionally followed by an import of the module:: Py_ExitStatusException(status); } +.. note:: + + If you need to define *static* extension types rather than heap-allocated + types, the :c:data:`Py_mod_multiple_interpreters` slot in the example should + be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}``. Without it, the module + can create the same problems as the legacy single-phase initialization, when + removing entries from ``sys.modules`` or importing compiled modules into + multiple interpreters within a process (or following a :c:func:`fork` without an + intervening :c:func:`exec`). .. _compilation: From 97eae130a9d86c14b0ede53a187ec410dca09c4b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 19:16:57 +0900 Subject: [PATCH 05/23] Suggested module names --- Doc/extending/embedding.rst | 4 ++-- Doc/extending/extending.rst | 4 ++-- Doc/includes/newtypes/custom.c | 4 ++-- Doc/includes/newtypes/custom2.c | 4 ++-- Doc/includes/newtypes/custom3.c | 4 ++-- Doc/includes/newtypes/custom4.c | 4 ++-- Doc/includes/newtypes/sublist.c | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Doc/extending/embedding.rst b/Doc/extending/embedding.rst index cafb9384a9e1fb..a8e4d20d8bc472 100644 --- a/Doc/extending/embedding.rst +++ b/Doc/extending/embedding.rst @@ -257,7 +257,7 @@ Python extension. For example:: {0, NULL} }; - struct PyModuleDef embmodule = { + struct PyModuleDef emb_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "emb", .m_size = 0, @@ -268,7 +268,7 @@ Python extension. For example:: static PyObject* PyInit_emb(void) { - return PyModuleDef_Init(&embmodule); + return PyModuleDef_Init(&emb_module); } Insert the above code just above the :c:func:`main` function. Also, insert the diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 738825c7e2d4ef..8e7f7f804b8f75 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -375,7 +375,7 @@ The Module's Method Table and Initialization Function I promised to show how :c:func:`!spam_system` is called from Python programs. First, we need to list its name and address in a "method table":: - static PyMethodDef SpamMethods[] = { + static PyMethodDef spam_methods[] = { ... {"system", spam_system, METH_VARARGS, "Execute a shell command."}, @@ -400,7 +400,7 @@ function. The method table must be referenced in the module definition structure:: - static struct PyModuleDef spammodule = { + static struct PyModuleDef spam_module = { ... .m_methods = spam_methods, ... diff --git a/Doc/includes/newtypes/custom.c b/Doc/includes/newtypes/custom.c index e67e57420c8155..6a707f38238374 100644 --- a/Doc/includes/newtypes/custom.c +++ b/Doc/includes/newtypes/custom.c @@ -38,7 +38,7 @@ static PyModuleDef_Slot custom_module_slots[] = { {0, NULL} }; -static PyModuleDef custommodule = { +static PyModuleDef custom_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "custom", .m_doc = "Example module that creates an extension type.", @@ -49,5 +49,5 @@ static PyModuleDef custommodule = { PyMODINIT_FUNC PyInit_custom(void) { - return PyModuleDef_Init(&custommodule); + return PyModuleDef_Init(&custom_module); } diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c index d9b14f27e934cf..a31219c6afe85c 100644 --- a/Doc/includes/newtypes/custom2.c +++ b/Doc/includes/newtypes/custom2.c @@ -128,7 +128,7 @@ static PyModuleDef_Slot custom_module_slots[] = { {0, NULL} }; -static PyModuleDef custommodule = { +static PyModuleDef custom_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "custom2", .m_doc = "Example module that creates an extension type.", @@ -139,5 +139,5 @@ static PyModuleDef custommodule = { PyMODINIT_FUNC PyInit_custom2(void) { - return PyModuleDef_Init(&custommodule); + return PyModuleDef_Init(&custom_module); } diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c index 6f6bf8ad260045..d2846c8c22d6f8 100644 --- a/Doc/includes/newtypes/custom3.c +++ b/Doc/includes/newtypes/custom3.c @@ -173,7 +173,7 @@ static PyModuleDef_Slot custom_module_slots[] = { {0, NULL} }; -static PyModuleDef custommodule = { +static PyModuleDef custom_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "custom3", .m_doc = "Example module that creates an extension type.", @@ -184,5 +184,5 @@ static PyModuleDef custommodule = { PyMODINIT_FUNC PyInit_custom3(void) { - return PyModuleDef_Init(&custommodule); + return PyModuleDef_Init(&custom_module); } diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c index afc87c8bbab157..b8d8f95fd3a2e1 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -192,7 +192,7 @@ static PyModuleDef_Slot custom_module_slots[] = { {0, NULL} }; -static PyModuleDef custommodule = { +static PyModuleDef custom_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "custom4", .m_doc = "Example module that creates an extension type.", @@ -203,5 +203,5 @@ static PyModuleDef custommodule = { PyMODINIT_FUNC PyInit_custom4(void) { - return PyModuleDef_Init(&custommodule); + return PyModuleDef_Init(&custom_module); } diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c index e3f792fa7ad921..cba147df1d479a 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -64,7 +64,7 @@ static PyModuleDef_Slot sublist_module_slots[] = { {0, NULL} }; -static PyModuleDef sublistmodule = { +static PyModuleDef sublist_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "sublist", .m_doc = "Example module that creates an extension type.", @@ -75,5 +75,5 @@ static PyModuleDef sublistmodule = { PyMODINIT_FUNC PyInit_sublist(void) { - return PyModuleDef_Init(&sublistmodule); + return PyModuleDef_Init(&sublist_module); } From 8ae6bae6ed4e98818700715e0b23c33455e2e397 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 19:19:09 +0900 Subject: [PATCH 06/23] Avoid modernization --- Doc/extending/extending.rst | 26 ++++++++++++++------------ Doc/extending/newtypes_tutorial.rst | 6 ++---- Doc/includes/newtypes/custom.c | 3 +-- Doc/includes/newtypes/custom2.c | 3 +-- Doc/includes/newtypes/custom3.c | 3 +-- Doc/includes/newtypes/custom4.c | 3 +-- Doc/includes/newtypes/sublist.c | 3 +-- 7 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 8e7f7f804b8f75..28d191ee24e822 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -95,10 +95,11 @@ shortly how it ends up being called):: spam_system(PyObject *self, PyObject *args) { const char *command; - if (!PyArg_ParseTuple(args, "s", &command)) { + int sts; + + if (!PyArg_ParseTuple(args, "s", &command)) return NULL; - } - int sts = system(command); + sts = system(command); return PyLong_FromLong(sts); } @@ -306,10 +307,11 @@ call to :c:func:`PyErr_SetString` as shown below:: spam_system(PyObject *self, PyObject *args) { const char *command; - if (!PyArg_ParseTuple(args, "s", &command)) { + int sts; + + if (!PyArg_ParseTuple(args, "s", &command)) return NULL; - } - int sts = system(command); + sts = system(command); if (sts < 0) { spam_state *state = PyModule_GetState(self); if (state != NULL) { @@ -329,9 +331,8 @@ Back to the Example Going back to our example function, you should now be able to understand this statement:: - if (!PyArg_ParseTuple(args, "s", &command)) { + if (!PyArg_ParseTuple(args, "s", &command)) return NULL; - } It returns ``NULL`` (the error indicator for functions returning object pointers) if an error is detected in the argument list, relying on the exception set by @@ -344,7 +345,7 @@ the variable :c:data:`!command` should properly be declared as ``const char The next statement is a call to the Unix function :c:func:`system`, passing it the string we just got from :c:func:`PyArg_ParseTuple`:: - int sts = system(command); + sts = system(command); Our :func:`!spam.system` function must return the value of :c:data:`!sts` as a Python object. This is done using the function :c:func:`PyLong_FromLong`. :: @@ -1285,10 +1286,11 @@ The function :c:func:`!spam_system` is modified in a trivial way:: spam_system(PyObject *self, PyObject *args) { const char *command; - if (!PyArg_ParseTuple(args, "s", &command)) { + int sts; + + if (!PyArg_ParseTuple(args, "s", &command)) return NULL; - } - int sts = PySpam_System(command); + sts = PySpam_System(command); return PyLong_FromLong(sts); } diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index a1a55e79b8599a..2ba49804644450 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -173,9 +173,8 @@ implementation provided by the API function :c:func:`PyType_GenericNew`. :: Everything else in the file should be familiar, except for some code in :c:func:`!custom_module_exec`:: - if (PyType_Ready(&CustomType) < 0) { + if (PyType_Ready(&CustomType) < 0) return -1; - } This initializes the :class:`!Custom` type, filling in a number of members to the appropriate default values, including :c:member:`~PyObject.ob_type` that we initially @@ -882,9 +881,8 @@ function:: sublist_module_exec(PyObject *mod) { SubListType.tp_base = &PyList_Type; - if (PyType_Ready(&SubListType) < 0) { + if (PyType_Ready(&SubListType) < 0) return -1; - } if (PyModule_AddObjectRef(mod, "SubList", (PyObject *)&SubListType) < 0) { return -1; diff --git a/Doc/includes/newtypes/custom.c b/Doc/includes/newtypes/custom.c index 6a707f38238374..6c7925e2c8ee89 100644 --- a/Doc/includes/newtypes/custom.c +++ b/Doc/includes/newtypes/custom.c @@ -19,9 +19,8 @@ static PyTypeObject CustomType = { static int custom_module_exec(PyObject *mod) { - if (PyType_Ready(&CustomType) < 0) { + if (PyType_Ready(&CustomType) < 0) return -1; - } if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { return -1; diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c index a31219c6afe85c..5e39566727a57a 100644 --- a/Doc/includes/newtypes/custom2.c +++ b/Doc/includes/newtypes/custom2.c @@ -109,9 +109,8 @@ static PyTypeObject CustomType = { static int custom_module_exec(PyObject *mod) { - if (PyType_Ready(&CustomType) < 0) { + if (PyType_Ready(&CustomType) < 0) return -1; - } if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { return -1; diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c index d2846c8c22d6f8..ac0ae14acb6707 100644 --- a/Doc/includes/newtypes/custom3.c +++ b/Doc/includes/newtypes/custom3.c @@ -154,9 +154,8 @@ static PyTypeObject CustomType = { static int custom_module_exec(PyObject *mod) { - if (PyType_Ready(&CustomType) < 0) { + if (PyType_Ready(&CustomType) < 0) return -1; - } if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { return -1; diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c index b8d8f95fd3a2e1..638c62c8eb4b0f 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -173,9 +173,8 @@ static PyTypeObject CustomType = { static int custom_module_exec(PyObject *mod) { - if (PyType_Ready(&CustomType) < 0) { + if (PyType_Ready(&CustomType) < 0) return -1; - } if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { return -1; diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c index cba147df1d479a..441f059901ac66 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -45,9 +45,8 @@ static int sublist_module_exec(PyObject *mod) { SubListType.tp_base = &PyList_Type; - if (PyType_Ready(&SubListType) < 0) { + if (PyType_Ready(&SubListType) < 0) return -1; - } if (PyModule_AddObjectRef(mod, "SubList", (PyObject *)&SubListType) < 0) { return -1; From bc782883f2ce7a03f3ffe541241c96598373cd5a Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 19:31:28 +0900 Subject: [PATCH 07/23] typo --- Doc/extending/extending.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 28d191ee24e822..41ed9b1f845565 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -483,8 +483,8 @@ optionally followed by an import of the module:: .. note:: If you need to define *static* extension types rather than heap-allocated - types, the :c:data:`Py_mod_multiple_interpreters` slot in the example should - be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}``. Without it, the module + types, the :c:data:`Py_mod_multiple_interpreters` slot in the examples should + be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED``. Without it, the module can create the same problems as the legacy single-phase initialization, when removing entries from ``sys.modules`` or importing compiled modules into multiple interpreters within a process (or following a :c:func:`fork` without an From c3ab0229c1d0034b51db8caf0d5d6587c03af6d8 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 20:36:47 +0900 Subject: [PATCH 08/23] Correction: "error" --- Doc/extending/extending.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 41ed9b1f845565..5825627dfd460c 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -364,7 +364,7 @@ macro):: return Py_None; :c:data:`Py_None` is the C name for the special Python object ``None``. It is a -genuine Python object rather than a ``NULL`` pointer, which means "SpamError" in most +genuine Python object rather than a ``NULL`` pointer, which means "error" in most contexts, as we have seen. From d65303c8e51e36750478623b5c79bc788a627378 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 21:07:43 +0900 Subject: [PATCH 09/23] Rephrase warning --- Doc/extending/extending.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 5825627dfd460c..4097960ce69a5e 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -482,13 +482,13 @@ optionally followed by an import of the module:: .. note:: - If you need to define *static* extension types rather than heap-allocated - types, the :c:data:`Py_mod_multiple_interpreters` slot in the examples should - be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED``. Without it, the module - can create the same problems as the legacy single-phase initialization, when - removing entries from ``sys.modules`` or importing compiled modules into - multiple interpreters within a process (or following a :c:func:`fork` without an - intervening :c:func:`exec`). + If you define *static* extension types rather than heap-allocated types, + the module can create the same problems as the legacy single-phase + initialization when removing entries from ``sys.modules`` or importing + compiled modules into multiple interpreters within a process + (or following a :c:func:`fork` without an intervening :c:func:`exec`). + At least, the :c:data:`Py_mod_multiple_interpreters` slot in the examples + should be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED``. .. _compilation: From aec6781a437b59d7130122d8432b38633b643f69 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Wed, 21 May 2025 22:02:08 +0900 Subject: [PATCH 10/23] keywdarg_module --- Doc/extending/extending.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 4097960ce69a5e..6fefa8a8147437 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -487,8 +487,8 @@ optionally followed by an import of the module:: initialization when removing entries from ``sys.modules`` or importing compiled modules into multiple interpreters within a process (or following a :c:func:`fork` without an intervening :c:func:`exec`). - At least, the :c:data:`Py_mod_multiple_interpreters` slot in the examples - should be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED``. + In this case, at least the :c:data:`Py_mod_multiple_interpreters` slot + in the examples should be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED``. .. _compilation: @@ -835,7 +835,7 @@ Philbrick (philbrick@hks.com):: {0, NULL} }; - static struct PyModuleDef keywdargmodule = { + static struct PyModuleDef keywdarg_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "keywdarg", .m_size = 0, @@ -846,7 +846,7 @@ Philbrick (philbrick@hks.com):: PyMODINIT_FUNC PyInit_keywdarg(void) { - return PyModuleDef_Init(&keywdargmodule); + return PyModuleDef_Init(&keywdarg_module); } From 7dfd5ad4db57acca50281cb28bb1b47d6964738b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 22 May 2025 13:40:59 +0900 Subject: [PATCH 11/23] Update Doc/extending/extending.rst Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/extending/extending.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 6fefa8a8147437..6c1df43142d8d8 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -20,10 +20,6 @@ source file by including the header ``"Python.h"``. The compilation of an extension module depends on its intended use as well as on your system setup; details are given in later chapters. -.. note:: - - The documentation is based on multi-phase initialization (:PEP:`489`) - since Python 3.14. See :file:`Modules/xxmodule.c` for more examples. .. note:: From f7c72dd0b552ce305706ad9e322c402e1cc44860 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 22 May 2025 13:44:16 +0900 Subject: [PATCH 12/23] Indicate xxlimited --- Doc/extending/extending.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 6c1df43142d8d8..aafa71d4d304ec 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -20,7 +20,6 @@ source file by including the header ``"Python.h"``. The compilation of an extension module depends on its intended use as well as on your system setup; details are given in later chapters. - .. note:: The C extension interface is specific to CPython, and extension modules do @@ -486,6 +485,10 @@ optionally followed by an import of the module:: In this case, at least the :c:data:`Py_mod_multiple_interpreters` slot in the examples should be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED``. +A more substantial example module is included in the Python source distribution +as :file:`Modules/xxlimited.c`. This file may be used as a template or simply +read as an example. + .. _compilation: Compilation and Linkage From c84638ca548d2fc35ab60042b3b634405c0b871b Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 22 May 2025 13:57:18 +0900 Subject: [PATCH 13/23] Add seealso to index --- Doc/extending/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/extending/index.rst b/Doc/extending/index.rst index 01b4df6d44acff..c1d8eb54ec1859 100644 --- a/Doc/extending/index.rst +++ b/Doc/extending/index.rst @@ -49,6 +49,10 @@ assistance from third party tools. It is intended primarily for creators of those tools, rather than being a recommended way to create your own C extensions. +.. seealso:: + + :pep:`489` -- Multi-phase extension module initialization + .. toctree:: :maxdepth: 2 :numbered: From 4e59e882da0f2a9709983046f876de6d32d91ed4 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 22 May 2025 16:05:24 +0900 Subject: [PATCH 14/23] Update Doc/extending/embedding.rst Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/extending/embedding.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/extending/embedding.rst b/Doc/extending/embedding.rst index a8e4d20d8bc472..0b7a76231c2d6c 100644 --- a/Doc/extending/embedding.rst +++ b/Doc/extending/embedding.rst @@ -252,8 +252,14 @@ Python extension. For example:: }; static PyModuleDef_Slot emb_module_slots[] = { + #ifdef Py_mod_multiple_interpreters + // signal that this module can be imported in isolated subinterpreters {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + #endif + #ifdef Py_mod_gil + // signal that this module supports running without an active GIL {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + #endif {0, NULL} }; From d68e3e33cd0681f46422cde28d75889aa04555cf Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 22 May 2025 16:13:03 +0900 Subject: [PATCH 15/23] ditto (for copy-paste) --- Doc/extending/extending.rst | 18 ++++++++++++++++++ Doc/includes/newtypes/custom.c | 4 ++++ Doc/includes/newtypes/custom2.c | 4 ++++ Doc/includes/newtypes/custom3.c | 4 ++++ Doc/includes/newtypes/custom4.c | 4 ++++ Doc/includes/newtypes/sublist.c | 4 ++++ 6 files changed, 38 insertions(+) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index aafa71d4d304ec..a3239b07d2c5db 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -259,8 +259,14 @@ with an exception object:: static PyModuleDef_Slot spam_module_slots[] = { {Py_mod_exec, spam_module_exec}, + #ifdef Py_mod_multiple_interpreters + // signal that this module can be imported in isolated subinterpreters {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + #endif + #ifdef Py_mod_gil + // signal that this module supports running without an active GIL {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + #endif {0, NULL} }; @@ -829,8 +835,12 @@ Philbrick (philbrick@hks.com):: }; static PyModuleDef_Slot keywdarg_slots[] = { + #ifdef Py_mod_multiple_interpreters {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + #endif + #ifdef Py_mod_gil {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + #endif {0, NULL} }; @@ -1333,8 +1343,12 @@ function must take care of initializing the C API pointer array:: static PyModuleDef_Slot spam_module_slots[] = { {Py_mod_exec, spam_module_exec}, + #ifdef Py_mod_multiple_interpreters {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + #endif + #ifdef Py_mod_gil {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + #endif {0, NULL} }; @@ -1412,8 +1426,12 @@ like this:: static PyModuleDef_Slot client_module_slots[] = { {Py_mod_exec, client_module_exec}, + #ifdef Py_mod_multiple_interpreters {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + #endif + #ifdef Py_mod_gil {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + #endif {0, NULL} }; diff --git a/Doc/includes/newtypes/custom.c b/Doc/includes/newtypes/custom.c index 6c7925e2c8ee89..e198c8b2a6e8a4 100644 --- a/Doc/includes/newtypes/custom.c +++ b/Doc/includes/newtypes/custom.c @@ -31,9 +31,13 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, +#ifdef Py_mod_multiple_interpreters // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, +#endif +#ifdef Py_mod_gil {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif {0, NULL} }; diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c index 5e39566727a57a..a7065ca2ffd969 100644 --- a/Doc/includes/newtypes/custom2.c +++ b/Doc/includes/newtypes/custom2.c @@ -121,9 +121,13 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, +#ifdef Py_mod_multiple_interpreters // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, +#endif +#ifdef Py_mod_gil {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif {0, NULL} }; diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c index ac0ae14acb6707..59cf122ec7977b 100644 --- a/Doc/includes/newtypes/custom3.c +++ b/Doc/includes/newtypes/custom3.c @@ -166,9 +166,13 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, +#ifdef Py_mod_multiple_interpreters // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, +#endif +#ifdef Py_mod_gil {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif {0, NULL} }; diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c index 638c62c8eb4b0f..bc3abc2d8ef4ff 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -185,9 +185,13 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, +#ifdef Py_mod_multiple_interpreters // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, +#endif +#ifdef Py_mod_gil {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif {0, NULL} }; diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c index 441f059901ac66..097a970c58f3a2 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -57,9 +57,13 @@ sublist_module_exec(PyObject *mod) static PyModuleDef_Slot sublist_module_slots[] = { {Py_mod_exec, sublist_module_exec}, +#ifdef Py_mod_multiple_interpreters // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, +#endif +#ifdef Py_mod_gil {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif {0, NULL} }; From f6465bda9c5aa0ad05f04c9722730645ed18c795 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Thu, 22 May 2025 22:39:11 +0900 Subject: [PATCH 16/23] Shrink slots --- Doc/c-api/intro.rst | 1 - Doc/extending/embedding.rst | 13 ----------- Doc/extending/extending.rst | 39 ++++----------------------------- Doc/includes/newtypes/custom.c | 7 +----- Doc/includes/newtypes/custom2.c | 7 +----- Doc/includes/newtypes/custom3.c | 7 +----- Doc/includes/newtypes/custom4.c | 7 +----- Doc/includes/newtypes/sublist.c | 7 +----- 8 files changed, 9 insertions(+), 79 deletions(-) diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index 13927137b91c7d..71c1b671b8f6d8 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -130,7 +130,6 @@ complete listing. .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", .m_size = 0, - .m_slots = spam_module_slots, ... }; diff --git a/Doc/extending/embedding.rst b/Doc/extending/embedding.rst index 0b7a76231c2d6c..135e829c60c51e 100644 --- a/Doc/extending/embedding.rst +++ b/Doc/extending/embedding.rst @@ -251,24 +251,11 @@ Python extension. For example:: {NULL, NULL, 0, NULL} }; - static PyModuleDef_Slot emb_module_slots[] = { - #ifdef Py_mod_multiple_interpreters - // signal that this module can be imported in isolated subinterpreters - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - #endif - #ifdef Py_mod_gil - // signal that this module supports running without an active GIL - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - #endif - {0, NULL} - }; - struct PyModuleDef emb_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "emb", .m_size = 0, .m_methods = emb_module_methods, - .m_slots = emb_module_slots, }; static PyObject* diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index a3239b07d2c5db..840c50ecb67e75 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -259,14 +259,6 @@ with an exception object:: static PyModuleDef_Slot spam_module_slots[] = { {Py_mod_exec, spam_module_exec}, - #ifdef Py_mod_multiple_interpreters - // signal that this module can be imported in isolated subinterpreters - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - #endif - #ifdef Py_mod_gil - // signal that this module supports running without an active GIL - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - #endif {0, NULL} }; @@ -484,15 +476,15 @@ optionally followed by an import of the module:: .. note:: If you define *static* extension types rather than heap-allocated types, - the module can create the same problems as the legacy single-phase + the module can cause the same problems as the legacy single-phase initialization when removing entries from ``sys.modules`` or importing compiled modules into multiple interpreters within a process (or following a :c:func:`fork` without an intervening :c:func:`exec`). - In this case, at least the :c:data:`Py_mod_multiple_interpreters` slot - in the examples should be ``Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED``. + In this case, at least the module should reject subinterpreters by using + a :c:type:`PyModuleDef_Slot` (``Py_mod_multiple_interpreters``). A more substantial example module is included in the Python source distribution -as :file:`Modules/xxlimited.c`. This file may be used as a template or simply +as :file:`Modules/xxlimited.c`. This file may be used as a template or simply read as an example. .. _compilation: @@ -834,22 +826,11 @@ Philbrick (philbrick@hks.com):: {NULL, NULL, 0, NULL} /* sentinel */ }; - static PyModuleDef_Slot keywdarg_slots[] = { - #ifdef Py_mod_multiple_interpreters - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - #endif - #ifdef Py_mod_gil - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - #endif - {0, NULL} - }; - static struct PyModuleDef keywdarg_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "keywdarg", .m_size = 0, .m_methods = keywdarg_methods, - .m_slots = keywdarg_slots, }; PyMODINIT_FUNC @@ -1343,12 +1324,6 @@ function must take care of initializing the C API pointer array:: static PyModuleDef_Slot spam_module_slots[] = { {Py_mod_exec, spam_module_exec}, - #ifdef Py_mod_multiple_interpreters - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - #endif - #ifdef Py_mod_gil - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - #endif {0, NULL} }; @@ -1426,12 +1401,6 @@ like this:: static PyModuleDef_Slot client_module_slots[] = { {Py_mod_exec, client_module_exec}, - #ifdef Py_mod_multiple_interpreters - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - #endif - #ifdef Py_mod_gil - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, - #endif {0, NULL} }; diff --git a/Doc/includes/newtypes/custom.c b/Doc/includes/newtypes/custom.c index e198c8b2a6e8a4..0879577c37f551 100644 --- a/Doc/includes/newtypes/custom.c +++ b/Doc/includes/newtypes/custom.c @@ -31,13 +31,8 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, -#ifdef Py_mod_multiple_interpreters - // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, -#endif -#ifdef Py_mod_gil - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, -#endif {0, NULL} }; diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c index a7065ca2ffd969..8c3b62ce098fba 100644 --- a/Doc/includes/newtypes/custom2.c +++ b/Doc/includes/newtypes/custom2.c @@ -121,13 +121,8 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, -#ifdef Py_mod_multiple_interpreters - // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, -#endif -#ifdef Py_mod_gil - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, -#endif {0, NULL} }; diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c index 59cf122ec7977b..04b2a680ef3414 100644 --- a/Doc/includes/newtypes/custom3.c +++ b/Doc/includes/newtypes/custom3.c @@ -166,13 +166,8 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, -#ifdef Py_mod_multiple_interpreters - // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, -#endif -#ifdef Py_mod_gil - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, -#endif {0, NULL} }; diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c index bc3abc2d8ef4ff..58190be08ac761 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -185,13 +185,8 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, -#ifdef Py_mod_multiple_interpreters - // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, -#endif -#ifdef Py_mod_gil - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, -#endif {0, NULL} }; diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c index 097a970c58f3a2..c91e6f08e3ac67 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -57,13 +57,8 @@ sublist_module_exec(PyObject *mod) static PyModuleDef_Slot sublist_module_slots[] = { {Py_mod_exec, sublist_module_exec}, -#ifdef Py_mod_multiple_interpreters - // Py_MOD_PER_INTERPRETER_GIL_SUPPORTED requires heaptypes + // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, -#endif -#ifdef Py_mod_gil - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, -#endif {0, NULL} }; From 1a65816621cecdfc93a4b510326bc644108cf9df Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 23 May 2025 06:50:28 +0900 Subject: [PATCH 17/23] Remove module states --- Doc/extending/extending.rst | 117 ++++++++++------------------ Doc/extending/newtypes_tutorial.rst | 6 +- Doc/includes/newtypes/custom.c | 4 +- Doc/includes/newtypes/custom2.c | 5 +- Doc/includes/newtypes/custom3.c | 5 +- Doc/includes/newtypes/custom4.c | 5 +- Doc/includes/newtypes/sublist.c | 5 +- 7 files changed, 53 insertions(+), 94 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 840c50ecb67e75..2dac6088317980 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -204,59 +204,26 @@ value must be in a particular range or must satisfy other conditions, :c:data:`PyExc_ValueError` is appropriate. You can also define a new exception that is unique to your module. For this, you -usually declare an object variable in the module's state and initialize it -in the module's :c:data:`Py_mod_exec` function (:c:func:`!spam_module_exec`) -with an exception object:: - - typedef struct { - PyObject *SpamError; - } spam_state; +usually declare a static object variable at the beginning of your file:: - static int - spam_module_exec(PyObject *module) - { - spam_state *state = PyModule_GetState(module); - if (state == NULL) { - return -1; - } + static PyObject *SpamError; - state->SpamError = PyErr_NewException("spam.error", NULL, NULL); - if (state->SpamError == NULL) { - return -1; - } - if (PyModule_AddObjectRef(module, "SpamError", state->SpamError) < 0) { - return -1; // followed by spam_module_free() then Py_CLEAR() - } +and initialize it in the module's :c:data:`Py_mod_exec` function +(:c:func:`!spam_module_exec`) - return 0; - } +with an exception object:: static int - spam_module_traverse(PyObject *module, visitproc visit, void *arg) { - spam_state *state = PyModule_GetState(module); - if (state == NULL) { + spam_module_exec(PyObject *m) + { + SpamError = PyErr_NewException("spam.error", NULL, NULL); + if (PyModule_AddObjectRef(m, "error", SpamError) < 0) { return -1; } - Py_VISIT(st->SpamError); - return 0; - } - static int - spam_module_clear(PyObject *module) { - spam_state *state = PyModule_GetState(module); - if (state == NULL) { - return -1; - } - Py_CLEAR(st->SpamError); return 0; } - static void - spam_module_free(void *module) - { - (void)spam_module_clear((PyObject *)module); - } - static PyModuleDef_Slot spam_module_slots[] = { {Py_mod_exec, spam_module_exec}, {0, NULL} @@ -265,11 +232,8 @@ with an exception object:: static struct PyModuleDef spam_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", - .m_size = sizeof(spam_state), // size of per-module state + .m_size = 0, // non-negative .m_slots = spam_module_slots, - .m_traverse = spam_module_traverse, - .m_clear = spam_module_clear, - .m_free = spam_module_free, }; PyMODINIT_FUNC @@ -306,10 +270,7 @@ call to :c:func:`PyErr_SetString` as shown below:: return NULL; sts = system(command); if (sts < 0) { - spam_state *state = PyModule_GetState(self); - if (state != NULL) { - PyErr_SetString(state->SpamError, "System command failed"); - } + PyErr_SetString(SpamError, "System command failed"); return NULL; } return PyLong_FromLong(sts); @@ -1297,25 +1258,19 @@ The ``#define`` is used to tell the header file that it is being included in the exporting module, not a client module. Finally, the module's initialization function must take care of initializing the C API pointer array:: - typedef struct { - void *PySpam_API[PySpam_API_pointers]; - } spam_state; - static int - spam_module_exec(PyObject *module) + spam_module_exec(PyObject *m) { - spam_state *state = PyModule_GetState(module); - if (state == NULL) { - return -1; - } + static void *PySpam_API[PySpam_API_pointers]; + PyObject *c_api_object; /* Initialize the C API pointer array */ - state->PySpam_API[PySpam_System_NUM] = PySpam_System; + PySpam_API[PySpam_System_NUM] = (void *)PySpam_System; /* Create a Capsule containing the API pointer array's address */ - PyObject *capi = PyCapsule_New(state->PySpam_API, "spam._C_API", NULL); - if (PyModule_Add(module, "_C_API", capi) < 0) { - Py_CLEAR(capi); + c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL); + + if (PyModule_Add(m, "_C_API", c_api_object) < 0) { return -1; } @@ -1330,7 +1285,7 @@ function must take care of initializing the C API pointer array:: static struct PyModuleDef spam_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", - .m_size = sizeof(spam_state), + .m_size = 0, .m_slots = spam_module_slots, }; @@ -1340,6 +1295,9 @@ function must take care of initializing the C API pointer array:: return PyModuleDef_Init(&spam_module); } +Note that ``PySpam_API`` is declared ``static``; otherwise the pointer +array would disappear when :c:func:`!PyInit_spam` terminates! + The bulk of the work is in the header file :file:`spammodule.h`, which looks like this:: @@ -1368,9 +1326,21 @@ like this:: #else /* This section is used in modules that use spammodule's API */ + static void **PySpam_API; + #define PySpam_System \ (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM]) + /* Return -1 on error, 0 on success. + * PyCapsule_Import will set an exception if there's an error. + */ + static int + import_spam(void) + { + PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0); + return (PySpam_API != NULL) ? 0 : -1; + } + #endif #ifdef __cplusplus @@ -1379,22 +1349,15 @@ like this:: #endif /* !defined(Py_SPAMMODULE_H) */ - - typedef struct { - void **PySpam_API; - } client_state; +All that a client module must do in order to have access to the function +:c:func:`!PySpam_System` is to call the function (or rather macro) +:c:func:`!import_spam` in its initialization function:: static int - client_module_exec(PyObject *module) + client_module_exec(PyObject *m) { - client_state *state = PyModule_GetState(module); - if (state == NULL) { + if (import_spam() < 0) return -1; - } - state->PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0); - if (state->PySpam_API == NULL) { - return -1; - } /* additional initialization can happen here */ return 0; } @@ -1407,7 +1370,7 @@ like this:: static struct PyModuleDef client_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "client", - .m_size = sizeof(client_state), + .m_size = 0, .m_slots = client_module_slots, }; diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index 2ba49804644450..1ab4451047b49a 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -180,7 +180,7 @@ This initializes the :class:`!Custom` type, filling in a number of members to the appropriate default values, including :c:member:`~PyObject.ob_type` that we initially set to ``NULL``. :: - if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; } @@ -878,13 +878,13 @@ issues, you can't fill that field directly with a reference to function:: static int - sublist_module_exec(PyObject *mod) + sublist_module_exec(PyObject *m) { SubListType.tp_base = &PyList_Type; if (PyType_Ready(&SubListType) < 0) return -1; - if (PyModule_AddObjectRef(mod, "SubList", (PyObject *)&SubListType) < 0) { + if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) { return -1; } diff --git a/Doc/includes/newtypes/custom.c b/Doc/includes/newtypes/custom.c index 0879577c37f551..aea6305947d34b 100644 --- a/Doc/includes/newtypes/custom.c +++ b/Doc/includes/newtypes/custom.c @@ -17,12 +17,12 @@ static PyTypeObject CustomType = { }; static int -custom_module_exec(PyObject *mod) +custom_module_exec(PyObject *m) { if (PyType_Ready(&CustomType) < 0) return -1; - if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; } diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c index 8c3b62ce098fba..0606e940ef683e 100644 --- a/Doc/includes/newtypes/custom2.c +++ b/Doc/includes/newtypes/custom2.c @@ -107,12 +107,12 @@ static PyTypeObject CustomType = { }; static int -custom_module_exec(PyObject *mod) +custom_module_exec(PyObject *m) { if (PyType_Ready(&CustomType) < 0) return -1; - if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; } @@ -121,7 +121,6 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, - // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, {0, NULL} }; diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c index 04b2a680ef3414..9971b7f0d21d7b 100644 --- a/Doc/includes/newtypes/custom3.c +++ b/Doc/includes/newtypes/custom3.c @@ -152,12 +152,12 @@ static PyTypeObject CustomType = { }; static int -custom_module_exec(PyObject *mod) +custom_module_exec(PyObject *m) { if (PyType_Ready(&CustomType) < 0) return -1; - if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; } @@ -166,7 +166,6 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, - // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, {0, NULL} }; diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c index 58190be08ac761..278cd03f8c7e6f 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -171,12 +171,12 @@ static PyTypeObject CustomType = { }; static int -custom_module_exec(PyObject *mod) +custom_module_exec(PyObject *m) { if (PyType_Ready(&CustomType) < 0) return -1; - if (PyModule_AddObjectRef(mod, "Custom", (PyObject *)&CustomType) < 0) { + if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; } @@ -185,7 +185,6 @@ custom_module_exec(PyObject *mod) static PyModuleDef_Slot custom_module_slots[] = { {Py_mod_exec, custom_module_exec}, - // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, {0, NULL} }; diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c index c91e6f08e3ac67..c6e4debcb7d984 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -42,13 +42,13 @@ static PyTypeObject SubListType = { }; static int -sublist_module_exec(PyObject *mod) +sublist_module_exec(PyObject *m) { SubListType.tp_base = &PyList_Type; if (PyType_Ready(&SubListType) < 0) return -1; - if (PyModule_AddObjectRef(mod, "SubList", (PyObject *)&SubListType) < 0) { + if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) { return -1; } @@ -57,7 +57,6 @@ sublist_module_exec(PyObject *mod) static PyModuleDef_Slot sublist_module_slots[] = { {Py_mod_exec, sublist_module_exec}, - // Just use this while using static types {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, {0, NULL} }; From a83e26624e303cf2c0e3e32f69258f9f03894618 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 23 May 2025 07:10:30 +0900 Subject: [PATCH 18/23] Correction: "SpamError" --- Doc/extending/extending.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 2dac6088317980..1187ffd2b1f78d 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -217,7 +217,7 @@ with an exception object:: spam_module_exec(PyObject *m) { SpamError = PyErr_NewException("spam.error", NULL, NULL); - if (PyModule_AddObjectRef(m, "error", SpamError) < 0) { + if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) { return -1; } From 2d1095887f209e3c9afde774b133913f68410390 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 23 May 2025 08:02:33 +0900 Subject: [PATCH 19/23] Add a Py_mod_multiple_interpreters slot --- Doc/extending/extending.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 1187ffd2b1f78d..a1353983034f6a 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -436,13 +436,13 @@ optionally followed by an import of the module:: .. note:: - If you define *static* extension types rather than heap-allocated types, - the module can cause the same problems as the legacy single-phase - initialization when removing entries from ``sys.modules`` or importing - compiled modules into multiple interpreters within a process - (or following a :c:func:`fork` without an intervening :c:func:`exec`). - In this case, at least the module should reject subinterpreters by using - a :c:type:`PyModuleDef_Slot` (``Py_mod_multiple_interpreters``). + If you declare a global variable or a local static one, the module can + cause the same problems as the legacy single-phase initialization when + removing entries from ``sys.modules`` or importing compiled modules into + multiple interpreters within a process (or following a :c:func:`fork` without an + intervening :c:func:`exec`). In this case, at least the module should + stop supporting subinterpreters through a :c:type:`PyModuleDef_Slot` + (:c:data:`Py_mod_multiple_interpreters`). A more substantial example module is included in the Python source distribution as :file:`Modules/xxlimited.c`. This file may be used as a template or simply @@ -1279,6 +1279,8 @@ function must take care of initializing the C API pointer array:: static PyModuleDef_Slot spam_module_slots[] = { {Py_mod_exec, spam_module_exec}, + // Just use this while using a local static variable + {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, {0, NULL} }; From a28e67c3aa12b2d571d45304b1c2b3023b9cde3e Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 23 May 2025 08:18:37 +0900 Subject: [PATCH 20/23] Cancel the prvious addition as needless --- Doc/extending/extending.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index a1353983034f6a..0e67fa049fc696 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -1279,8 +1279,6 @@ function must take care of initializing the C API pointer array:: static PyModuleDef_Slot spam_module_slots[] = { {Py_mod_exec, spam_module_exec}, - // Just use this while using a local static variable - {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, {0, NULL} }; From d35a6b4d68fd5fa5ecbe051ac4e0d98d9d009fc8 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 26 May 2025 21:17:19 +0100 Subject: [PATCH 21/23] updates --- Doc/c-api/allocation.rst | 2 +- Doc/c-api/intro.rst | 1 - Doc/extending/building.rst | 6 +- Doc/extending/embedding.rst | 2 +- Doc/extending/extending.rst | 89 +++++++++++------------------ Doc/extending/newtypes_tutorial.rst | 9 ++- Doc/includes/newtypes/custom.c | 3 +- Doc/includes/newtypes/custom2.c | 3 +- Doc/includes/newtypes/custom3.c | 3 +- Doc/includes/newtypes/custom4.c | 3 +- Doc/includes/newtypes/sublist.c | 3 +- 11 files changed, 53 insertions(+), 71 deletions(-) diff --git a/Doc/c-api/allocation.rst b/Doc/c-api/allocation.rst index f8d01a3f29b30e..59d913a0462382 100644 --- a/Doc/c-api/allocation.rst +++ b/Doc/c-api/allocation.rst @@ -153,6 +153,6 @@ Allocating Objects on the Heap .. seealso:: - :c:func:`PyModule_Create` + :ref:`moduleobjects` To allocate and create extension modules. diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index 71c1b671b8f6d8..112b141032f55a 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -129,7 +129,6 @@ complete listing. static struct PyModuleDef spam_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", - .m_size = 0, ... }; diff --git a/Doc/extending/building.rst b/Doc/extending/building.rst index ddde567f6f3efa..a58eb40d431c59 100644 --- a/Doc/extending/building.rst +++ b/Doc/extending/building.rst @@ -23,10 +23,10 @@ instance. See :ref:`initializing-modules` for details. .. highlight:: python For modules with ASCII-only names, the function must be named -``PyInit_``, with ```` replaced by the name of the -module. When using :ref:`multi-phase-initialization`, non-ASCII module names +:samp:`PyInit_{}`, with ```` replaced by the name of the module. +When using :ref:`multi-phase-initialization`, non-ASCII module names are allowed. In this case, the initialization function name is -``PyInitU_``, with ```` encoded using Python's +:samp:`PyInitU_{}`, with ```` encoded using Python's *punycode* encoding with hyphens replaced by underscores. In Python:: def initfunc_name(name): diff --git a/Doc/extending/embedding.rst b/Doc/extending/embedding.rst index 135e829c60c51e..cb41889437c8b0 100644 --- a/Doc/extending/embedding.rst +++ b/Doc/extending/embedding.rst @@ -251,7 +251,7 @@ Python extension. For example:: {NULL, NULL, 0, NULL} }; - struct PyModuleDef emb_module = { + static struct PyModuleDef emb_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "emb", .m_size = 0, diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 0e67fa049fc696..57ec9c05e5f996 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -203,24 +203,32 @@ function usually raises :c:data:`PyExc_TypeError`. If you have an argument whos value must be in a particular range or must satisfy other conditions, :c:data:`PyExc_ValueError` is appropriate. -You can also define a new exception that is unique to your module. For this, you -usually declare a static object variable at the beginning of your file:: +You can also define a new exception that is unique to your module. +For this, you usually declare an object variable at in the module state:: - static PyObject *SpamError; + typedef struct { + // ... + PyObject *SpamError; + // ... + } spam_state; and initialize it in the module's :c:data:`Py_mod_exec` function -(:c:func:`!spam_module_exec`) - -with an exception object:: +(:c:func:`!spam_module_exec`) with an exception object:: static int spam_module_exec(PyObject *m) { - SpamError = PyErr_NewException("spam.error", NULL, NULL); - if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) { + spam_state *state = PyModule_GetState(m); + if (state == NULL) { + return -1; + } + state->SpamError = PyErr_NewException("spam.error", NULL, NULL); + if (state->SpamError == NULL) { + return -1; + } + if (PyModule_AddType(m, (PyTypeObject *)state->SpamError) < 0) { return -1; } - return 0; } @@ -232,7 +240,7 @@ with an exception object:: static struct PyModuleDef spam_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", - .m_size = 0, // non-negative + .m_size = sizeof(spam_state), .m_slots = spam_module_slots, }; @@ -436,18 +444,20 @@ optionally followed by an import of the module:: .. note:: - If you declare a global variable or a local static one, the module can - cause the same problems as the legacy single-phase initialization when + If you declare a global variable or a local static one, the module may + experience unintended side-effects on re-initialisation, for example when removing entries from ``sys.modules`` or importing compiled modules into - multiple interpreters within a process (or following a :c:func:`fork` without an - intervening :c:func:`exec`). In this case, at least the module should - stop supporting subinterpreters through a :c:type:`PyModuleDef_Slot` - (:c:data:`Py_mod_multiple_interpreters`). + multiple interpreters within a process + (or following a :c:func:`fork` without an intervening :c:func:`exec`). + If module state is not yet fully :ref:`isolated `, + authors should consider marking the module as having no support for subinterpreters + (via :c:macro:`Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED`). A more substantial example module is included in the Python source distribution as :file:`Modules/xxlimited.c`. This file may be used as a template or simply read as an example. + .. _compilation: Compilation and Linkage @@ -1068,8 +1078,9 @@ why his :meth:`!__del__` methods would fail... The second case of problems with a borrowed reference is a variant involving threads. Normally, multiple threads in the Python interpreter can't get in each -other's way, because there is a global lock protecting Python's entire object -space. However, it is possible to temporarily release this lock using the macro +other's way, because there is a :term:`global lock ` +protecting Python's entire object space. +However, it is possible to temporarily release this lock using the macro :c:macro:`Py_BEGIN_ALLOW_THREADS`, and to re-acquire it using :c:macro:`Py_END_ALLOW_THREADS`. This is common around blocking I/O calls, to let other threads use the processor while waiting for the I/O to complete. @@ -1255,8 +1266,8 @@ two more lines must be added:: #include "spammodule.h" The ``#define`` is used to tell the header file that it is being included in the -exporting module, not a client module. Finally, the module's initialization -function must take care of initializing the C API pointer array:: +exporting module, not a client module. Finally, the module's :c:data:`mod_exec +` function must take care of initializing the C API pointer array:: static int spam_module_exec(PyObject *m) @@ -1277,24 +1288,6 @@ function must take care of initializing the C API pointer array:: return 0; } - static PyModuleDef_Slot spam_module_slots[] = { - {Py_mod_exec, spam_module_exec}, - {0, NULL} - }; - - static struct PyModuleDef spam_module = { - .m_base = PyModuleDef_HEAD_INIT, - .m_name = "spam", - .m_size = 0, - .m_slots = spam_module_slots, - }; - - PyMODINIT_FUNC - PyInit_spam(void) - { - return PyModuleDef_Init(&spam_module); - } - Note that ``PySpam_API`` is declared ``static``; otherwise the pointer array would disappear when :c:func:`!PyInit_spam` terminates! @@ -1351,7 +1344,7 @@ like this:: All that a client module must do in order to have access to the function :c:func:`!PySpam_System` is to call the function (or rather macro) -:c:func:`!import_spam` in its initialization function:: +:c:func:`!import_spam` in its :c:data:`mod_exec ` function:: static int client_module_exec(PyObject *m) @@ -1362,24 +1355,6 @@ All that a client module must do in order to have access to the function return 0; } - static PyModuleDef_Slot client_module_slots[] = { - {Py_mod_exec, client_module_exec}, - {0, NULL} - }; - - static struct PyModuleDef client_module = { - .m_base = PyModuleDef_HEAD_INIT, - .m_name = "client", - .m_size = 0, - .m_slots = client_module_slots, - }; - - PyMODINIT_FUNC - PyInit_client(void) - { - return PyModuleDef_Init(&client_module); - } - The main disadvantage of this approach is that the file :file:`spammodule.h` is rather complicated. However, the basic structure is the same for each function that is exported, so it has to be learned only once. diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index 1ab4451047b49a..a735d484404def 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -55,8 +55,10 @@ from the previous chapter. This file defines three things: #. How the :class:`!Custom` **type** behaves: this is the ``CustomType`` struct, which defines a set of flags and function pointers that the interpreter inspects when specific operations are requested. -#. How to initialize the :mod:`!custom` module: this is the ``PyInit_custom`` - function and the associated ``custommodule`` struct. +#. How to define and execute the :mod:`!custom` module: this is the + ``PyInit_custom`` function and the associated ``custom_module`` struct for + defining the module, and the ``custom_module_exec`` function to set up + a fresh module object. The first bit is:: @@ -173,8 +175,9 @@ implementation provided by the API function :c:func:`PyType_GenericNew`. :: Everything else in the file should be familiar, except for some code in :c:func:`!custom_module_exec`:: - if (PyType_Ready(&CustomType) < 0) + if (PyType_Ready(&CustomType) < 0) { return -1; + } This initializes the :class:`!Custom` type, filling in a number of members to the appropriate default values, including :c:member:`~PyObject.ob_type` that we initially diff --git a/Doc/includes/newtypes/custom.c b/Doc/includes/newtypes/custom.c index aea6305947d34b..039a1a7219349c 100644 --- a/Doc/includes/newtypes/custom.c +++ b/Doc/includes/newtypes/custom.c @@ -19,8 +19,9 @@ static PyTypeObject CustomType = { static int custom_module_exec(PyObject *m) { - if (PyType_Ready(&CustomType) < 0) + if (PyType_Ready(&CustomType) < 0) { return -1; + } if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; diff --git a/Doc/includes/newtypes/custom2.c b/Doc/includes/newtypes/custom2.c index 0606e940ef683e..1ff8e707d1b0a0 100644 --- a/Doc/includes/newtypes/custom2.c +++ b/Doc/includes/newtypes/custom2.c @@ -109,8 +109,9 @@ static PyTypeObject CustomType = { static int custom_module_exec(PyObject *m) { - if (PyType_Ready(&CustomType) < 0) + if (PyType_Ready(&CustomType) < 0) { return -1; + } if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; diff --git a/Doc/includes/newtypes/custom3.c b/Doc/includes/newtypes/custom3.c index 9971b7f0d21d7b..22f50eb0e1de89 100644 --- a/Doc/includes/newtypes/custom3.c +++ b/Doc/includes/newtypes/custom3.c @@ -154,8 +154,9 @@ static PyTypeObject CustomType = { static int custom_module_exec(PyObject *m) { - if (PyType_Ready(&CustomType) < 0) + if (PyType_Ready(&CustomType) < 0) { return -1; + } if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; diff --git a/Doc/includes/newtypes/custom4.c b/Doc/includes/newtypes/custom4.c index 278cd03f8c7e6f..07585aff5987f3 100644 --- a/Doc/includes/newtypes/custom4.c +++ b/Doc/includes/newtypes/custom4.c @@ -173,8 +173,9 @@ static PyTypeObject CustomType = { static int custom_module_exec(PyObject *m) { - if (PyType_Ready(&CustomType) < 0) + if (PyType_Ready(&CustomType) < 0) { return -1; + } if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) { return -1; diff --git a/Doc/includes/newtypes/sublist.c b/Doc/includes/newtypes/sublist.c index c6e4debcb7d984..b784456a4ef667 100644 --- a/Doc/includes/newtypes/sublist.c +++ b/Doc/includes/newtypes/sublist.c @@ -45,8 +45,9 @@ static int sublist_module_exec(PyObject *m) { SubListType.tp_base = &PyList_Type; - if (PyType_Ready(&SubListType) < 0) + if (PyType_Ready(&SubListType) < 0) { return -1; + } if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) { return -1; From 9cb3092b53e8435c2d4c6db96b22c599094608e7 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 26 May 2025 22:11:09 +0100 Subject: [PATCH 22/23] PEP 7 --- Doc/extending/extending.rst | 3 ++- Doc/extending/newtypes_tutorial.rst | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 57ec9c05e5f996..d0f6c42f28eba8 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -1349,8 +1349,9 @@ All that a client module must do in order to have access to the function static int client_module_exec(PyObject *m) { - if (import_spam() < 0) + if (import_spam() < 0) { return -1; + } /* additional initialization can happen here */ return 0; } diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst index a735d484404def..f14690de4f86e8 100644 --- a/Doc/extending/newtypes_tutorial.rst +++ b/Doc/extending/newtypes_tutorial.rst @@ -884,8 +884,9 @@ function:: sublist_module_exec(PyObject *m) { SubListType.tp_base = &PyList_Type; - if (PyType_Ready(&SubListType) < 0) + if (PyType_Ready(&SubListType) < 0) { return -1; + } if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) { return -1; From 6e5b1c59c793eef7d041a531155a0aa9d0ca9018 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 26 May 2025 22:38:02 +0100 Subject: [PATCH 23/23] revert module state for SpamError --- Doc/extending/extending.rst | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index d0f6c42f28eba8..918c751b009761 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -204,31 +204,22 @@ value must be in a particular range or must satisfy other conditions, :c:data:`PyExc_ValueError` is appropriate. You can also define a new exception that is unique to your module. -For this, you usually declare an object variable at in the module state:: +For this, you can declare a static global object variable at the beginning +of the file:: - typedef struct { - // ... - PyObject *SpamError; - // ... - } spam_state; + static PyObject *SpamError; -and initialize it in the module's :c:data:`Py_mod_exec` function -(:c:func:`!spam_module_exec`) with an exception object:: +and initialize it with an exception object in the module's +:c:data:`Py_mod_exec` function (:c:func:`!spam_module_exec`):: static int spam_module_exec(PyObject *m) { - spam_state *state = PyModule_GetState(m); - if (state == NULL) { - return -1; - } - state->SpamError = PyErr_NewException("spam.error", NULL, NULL); - if (state->SpamError == NULL) { - return -1; - } - if (PyModule_AddType(m, (PyTypeObject *)state->SpamError) < 0) { + SpamError = PyErr_NewException("spam.error", NULL, NULL); + if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) { return -1; } + return 0; } @@ -240,7 +231,7 @@ and initialize it in the module's :c:data:`Py_mod_exec` function static struct PyModuleDef spam_module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "spam", - .m_size = sizeof(spam_state), + .m_size = 0, // non-negative .m_slots = spam_module_slots, };