diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst index 6db20237f3fdb06..d0bd2289b675487 100644 --- a/Doc/c-api/import.rst +++ b/Doc/c-api/import.rst @@ -98,27 +98,46 @@ Importing Modules an exception set on failure (the module still exists in this case). -.. c:function:: PyObject* PyImport_AddModuleObject(PyObject *name) +.. c:function:: PyObject* PyImport_AddModuleRef(const char *name) + + Return the module object corresponding to a module name. + + The *name* argument may be of the form ``package.module``. First check the + modules dictionary if there's one there, and if not, create a new one and + insert it in the modules dictionary. + + Return a :term:`strong reference` to the module on success. Return ``NULL`` + with an exception set on failure. + + The module name *name* is decoded from UTF-8. + + This function does not load or import the module; if the module wasn't + already loaded, you will get an empty module object. Use + :c:func:`PyImport_ImportModule` or one of its variants to import a module. + Package structures implied by a dotted name for *name* are not created if + not already present. - Return the module object corresponding to a module name. The *name* argument - may be of the form ``package.module``. First check the modules dictionary if - there's one there, and if not, create a new one and insert it in the modules - dictionary. Return ``NULL`` with an exception set on failure. + .. versionadded:: 3.13 - .. note:: - This function does not load or import the module; if the module wasn't already - loaded, you will get an empty module object. Use :c:func:`PyImport_ImportModule` - or one of its variants to import a module. Package structures implied by a - dotted name for *name* are not created if not already present. +.. c:function:: PyObject* PyImport_AddModuleObject(PyObject *name) + + Similar to :c:func:`PyImport_AddModuleRef`, but return a :term:`borrowed + reference` and *name* is a Python :class:`str` object. .. versionadded:: 3.3 + .. deprecated-removed:: 3.13 3.15 + Use :c:func:`PyImport_AddModuleRef` instead. + .. c:function:: PyObject* PyImport_AddModule(const char *name) - Similar to :c:func:`PyImport_AddModuleObject`, but the name is a UTF-8 - encoded string instead of a Unicode object. + Similar to :c:func:`PyImport_AddModuleRef`, but return a :term:`borrowed + reference`. + + .. deprecated-removed:: 3.13 3.15 + Use :c:func:`PyImport_AddModuleRef` instead. .. c:function:: PyObject* PyImport_ExecCodeModule(const char *name, PyObject *co) diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index f5628abb90443c0..d707cc34c98c97a 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -974,6 +974,9 @@ PyCoro_New:PyFrameObject*:frame:0: PyCoro_New:PyObject*:name:0: PyCoro_New:PyObject*:qualname:0: +PyImport_AddModuleRef:PyObject*::+1: +PyImport_AddModuleRef:const char*:name:: + PyImport_AddModule:PyObject*::0:reference borrowed from sys.modules PyImport_AddModule:const char*:name:: diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 80806aedfcdbd5f..a3fde01cf67f3c5 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -298,6 +298,7 @@ type,PyGetSetDef,3.2,,full-abi var,PyGetSetDescr_Type,3.2,, function,PyImport_AddModule,3.2,, function,PyImport_AddModuleObject,3.7,, +function,PyImport_AddModuleRef,3.13,, function,PyImport_AppendInittab,3.2,, function,PyImport_ExecCodeModule,3.2,, function,PyImport_ExecCodeModuleEx,3.2,, diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index ef93848840861c3..8b2cb6d6f0a64ab 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -989,9 +989,11 @@ is less clear, here, however, since a few common routines are exceptions: :c:func:`PyDict_GetItemString` all return references that you borrow from the tuple, list or dictionary. -The function :c:func:`PyImport_AddModule` also returns a borrowed reference, even -though it may actually create the object it returns: this is possible because an -owned reference to the object is stored in ``sys.modules``. +The deprecated function :c:func:`PyImport_AddModule` also returns a +:term:`borrowed reference`, even though it may actually create the object it +returns: this is possible because an owned reference to the object is stored in +``sys.modules``. The new function :c:func:`PyImport_AddModuleRef` is +recommended since it returns a :term:`strong reference`. When you pass an object reference into another function, in general, the function borrows the reference from you --- if it needs to store it, it will use diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 735715f01528984..ba44c795d49a9dd 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -426,6 +426,11 @@ New Features APIs accepting the format codes always use ``Py_ssize_t`` for ``#`` formats. (Contributed by Inada Naoki in :gh:`104922`.) +* Add :c:func:`PyImport_AddModuleRef` function to replace the deprecated + :c:func:`PyImport_AddModule` function: return a :term:`strong reference` to + the module, instead of a :term:`borrowed reference`. + (Contributed by Victor Stinner in :gh:`105922`.) + Porting to Python 3.13 ---------------------- @@ -457,6 +462,12 @@ Deprecated Scheduled for removal in Python 3.15. (Contributed by Victor Stinner in :gh:`105396`.) +* Deprecate :c:func:`PyImport_AddModule` and :c:func:`PyImport_AddModuleObject` + functions which return a :term:`borrowed reference`. Replace them with the + new :c:func:`PyImport_AddModuleRef` function which returns a :term:`strong + reference`. + (Contributed by Victor Stinner in :gh:`105922`.) + Removed ------- diff --git a/Include/import.h b/Include/import.h index 6c63744edb06343..24b23b9119196fc 100644 --- a/Include/import.h +++ b/Include/import.h @@ -43,6 +43,11 @@ PyAPI_FUNC(PyObject *) PyImport_AddModuleObject( PyAPI_FUNC(PyObject *) PyImport_AddModule( const char *name /* UTF-8 encoded string */ ); +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000 +PyAPI_FUNC(PyObject *) PyImport_AddModuleRef( + const char *name /* UTF-8 encoded string */ + ); +#endif PyAPI_FUNC(PyObject *) PyImport_ImportModule( const char *name /* UTF-8 encoded string */ ); diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 71a50bcbeedf9bc..f2726da203efbda 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -2621,6 +2621,30 @@ def test_basic_multiple_interpreters_reset_each(self): # * module's global state was initialized, not reset +@cpython_only +class CAPITests(unittest.TestCase): + def test_pyimport_addmodule(self): + # gh-105922: Test PyImport_AddModuleRef(), PyImport_AddModule() + # and PyImport_AddModuleObject() + import _testcapi + for name in ( + 'sys', # frozen module + 'test', # package + __name__, # package.module + ): + _testcapi.check_pyimport_addmodule(name) + + def test_pyimport_addmodule_create(self): + # gh-105922: Test PyImport_AddModuleRef(), create a new module + import _testcapi + name = 'dontexist' + self.assertNotIn(name, sys.modules) + self.addCleanup(unload, name) + + mod = _testcapi.check_pyimport_addmodule(name) + self.assertIs(mod, sys.modules[name]) + + if __name__ == '__main__': # Test needs to be a package, so we can do relative imports. unittest.main() diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 8cad71c7c34545c..c26dff5aaf370bc 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -328,6 +328,7 @@ def test_windows_feature_macros(self): "PyGetSetDescr_Type", "PyImport_AddModule", "PyImport_AddModuleObject", + "PyImport_AddModuleRef", "PyImport_AppendInittab", "PyImport_ExecCodeModule", "PyImport_ExecCodeModuleEx", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index f9100054175fc1e..7025ed4e66b6985 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2428,3 +2428,5 @@ added = '3.12' [const.Py_TPFLAGS_ITEMS_AT_END] added = '3.12' +[function.PyImport_AddModuleRef] + added = '3.13' diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 35687b49f24a0d1..9d4517c8f68b09b 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3325,6 +3325,53 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args)) } +static PyObject * +check_pyimport_addmodule(PyObject *self, PyObject *args) +{ + const char *name; + if (!PyArg_ParseTuple(args, "s", &name)) { + return NULL; + } + + // test PyImport_AddModuleRef() + PyObject *module = PyImport_AddModuleRef(name); + if (module == NULL) { + return NULL; + } + assert(PyModule_Check(module)); + // module is a strong reference + + // test PyImport_AddModule() + PyObject *module2 = PyImport_AddModule(name); + if (module2 == NULL) { + goto error; + } + assert(PyModule_Check(module2)); + assert(module2 == module); + // module2 is a borrowed ref + + // test PyImport_AddModuleObject() + PyObject *name_obj = PyUnicode_FromString(name); + if (name_obj == NULL) { + goto error; + } + PyObject *module3 = PyImport_AddModuleObject(name_obj); + Py_DECREF(name_obj); + if (module3 == NULL) { + goto error; + } + assert(PyModule_Check(module3)); + assert(module3 == module); + // module3 is a borrowed ref + + return module; + +error: + Py_DECREF(module); + return NULL; +} + + static PyMethodDef TestMethods[] = { {"set_errno", set_errno, METH_VARARGS}, {"test_config", test_config, METH_NOARGS}, @@ -3468,6 +3515,7 @@ static PyMethodDef TestMethods[] = { {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL}, {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL}, {"test_atexit", test_atexit, METH_NOARGS}, + {"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/PC/python3dll.c b/PC/python3dll.c index 505feef4b986c41..fea19b77185dd54 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -288,6 +288,7 @@ EXPORT_FUNC(PyGILState_GetThisThreadState) EXPORT_FUNC(PyGILState_Release) EXPORT_FUNC(PyImport_AddModule) EXPORT_FUNC(PyImport_AddModuleObject) +EXPORT_FUNC(PyImport_AddModuleRef) EXPORT_FUNC(PyImport_AppendInittab) EXPORT_FUNC(PyImport_ExecCodeModule) EXPORT_FUNC(PyImport_ExecCodeModuleEx) diff --git a/Python/import.c b/Python/import.c index a81026dc3320a56..d3e29e37cd210da 100644 --- a/Python/import.c +++ b/Python/import.c @@ -350,6 +350,20 @@ import_add_module(PyThreadState *tstate, PyObject *name) return m; } +PyObject * +PyImport_AddModuleRef(const char *name) +{ + PyObject *name_obj = PyUnicode_FromString(name); + if (name_obj == NULL) { + return NULL; + } + PyThreadState *tstate = _PyThreadState_GET(); + PyObject *module = import_add_module(tstate, name_obj); + Py_DECREF(name_obj); + return module; +} + + PyObject * PyImport_AddModuleObject(PyObject *name) { @@ -359,6 +373,7 @@ PyImport_AddModuleObject(PyObject *name) return NULL; } + // PyImport_AddModuleObject() returns a borrowed reference // https://bugs.python.org/issue41994 PyObject *ref = PyWeakref_NewRef(mod, NULL); Py_DECREF(mod); diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index ff9694886b6a45d..ff2e4189f1bb4e7 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2172,13 +2172,14 @@ _Py_IsInterpreterFinalizing(PyInterpreterState *interp) static PyStatus add_main_module(PyInterpreterState *interp) { - PyObject *m, *d, *loader, *ann_dict; - m = PyImport_AddModule("__main__"); - if (m == NULL) + PyObject *module = PyImport_AddModuleRef("__main__"); + if (module == NULL) { return _PyStatus_ERR("can't create __main__ module"); + } + PyObject *d = PyModule_GetDict(module); + Py_DECREF(module); - d = PyModule_GetDict(m); - ann_dict = PyDict_New(); + PyObject *ann_dict = PyDict_New(); if ((ann_dict == NULL) || (PyDict_SetItemString(d, "__annotations__", ann_dict) < 0)) { return _PyStatus_ERR("Failed to initialize __main__.__annotations__"); @@ -2204,7 +2205,7 @@ add_main_module(PyInterpreterState *interp) * will be set if __main__ gets further initialized later in the startup * process. */ - loader = _PyDict_GetItemStringWithError(d, "__loader__"); + PyObject *loader = _PyDict_GetItemStringWithError(d, "__loader__"); if (loader == NULL || loader == Py_None) { if (PyErr_Occurred()) { return _PyStatus_ERR("Failed to test __main__.__loader__"); diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 05e7b4370869afd..88a68731d84d6db 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -185,7 +185,7 @@ static int PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename, PyCompilerFlags *flags) { - PyObject *m, *d, *v, *w, *oenc = NULL; + PyObject *v, *w, *oenc = NULL; mod_ty mod; PyArena *arena; const char *ps1 = "", *ps2 = "", *enc = NULL; @@ -251,17 +251,21 @@ PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename, } return -1; } - m = PyImport_AddModuleObject(&_Py_ID(__main__)); - if (m == NULL) { + + PyObject *module = PyImport_AddModuleRef("main"); + if (module == NULL) { _PyArena_Free(arena); return -1; } - d = PyModule_GetDict(m); + PyObject *d = PyModule_GetDict(module); + v = run_mod(mod, filename, d, d, flags, arena); _PyArena_Free(arena); + Py_DECREF(module); if (v == NULL) { return -1; } + Py_DECREF(v); flush_io(); return 0; @@ -376,14 +380,15 @@ int _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, PyCompilerFlags *flags) { - PyObject *m, *d, *v; + PyObject *d, *v; int set_file_name = 0, ret = -1; - m = PyImport_AddModule("__main__"); - if (m == NULL) + PyObject *module = PyImport_AddModuleRef("__main__"); + if (module == NULL) { return -1; - Py_INCREF(m); - d = PyModule_GetDict(m); + } + d = PyModule_GetDict(module); + if (_PyDict_GetItemStringWithError(d, "__file__") == NULL) { if (PyErr_Occurred()) { goto done; @@ -435,7 +440,7 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, } flush_io(); if (v == NULL) { - Py_CLEAR(m); + Py_CLEAR(module); PyErr_Print(); goto done; } @@ -450,7 +455,7 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, PyErr_Clear(); } } - Py_XDECREF(m); + Py_DECREF(module); return ret; } @@ -472,12 +477,14 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit, int PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags) { - PyObject *m, *d, *v; - m = PyImport_AddModule("__main__"); - if (m == NULL) + PyObject *module = PyImport_AddModuleRef("__main__"); + if (module == NULL) { return -1; - d = PyModule_GetDict(m); - v = PyRun_StringFlags(command, Py_file_input, d, d, flags); + } + PyObject *d = PyModule_GetDict(module); + Py_DECREF(module); + + PyObject *v = PyRun_StringFlags(command, Py_file_input, d, d, flags); if (v == NULL) { PyErr_Print(); return -1;