From ba44a2ea80c2ac9897f264748b3efa25e9ff05f6 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Mar 2026 15:27:17 +0100 Subject: [PATCH 1/2] gh-146063: Add PyObject_CallFinalizer() to the limited C API Add PyObject_CallFinalizer() and PyObject_CallFinalizerFromDealloc() to the limited C API. Add a test on PyObject_CallFinalizer(). --- Doc/data/stable_abi.dat | 2 ++ Doc/whatsnew/3.15.rst | 4 ++++ Include/cpython/object.h | 2 -- Include/object.h | 3 +++ Lib/test/test_capi/test_object.py | 15 ++++++++++++++ Lib/test/test_stable_abi_ctypes.py | 2 ++ ...-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst | 3 +++ Misc/stable_abi.toml | 6 +++++- Modules/_testlimitedcapi/object.c | 20 ++++++++++++------- PC/python3dll.c | 2 ++ 10 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 510e683c87e8b9..df603941ac1562 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -563,6 +563,8 @@ func,PyObject_ASCII,3.2,, func,PyObject_AsFileDescriptor,3.2,, func,PyObject_Bytes,3.2,, func,PyObject_Call,3.2,, +func,PyObject_CallFinalizer,3.15,, +func,PyObject_CallFinalizerFromDealloc,3.15,, func,PyObject_CallFunction,3.2,, func,PyObject_CallFunctionObjArgs,3.2,, func,PyObject_CallMethod,3.2,, diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 5689ecbffc4b30..a995c166917d68 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1662,6 +1662,10 @@ New features (Contributed by Victor Stinner in :gh:`141510`.) +* Add :c:func:`PyObject_CallFinalizer` and + :c:func:`PyObject_CallFinalizerFromDealloc` functions to the limited C API. + (Contributed by Victor Stinner in :gh:`146063`.) + * Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, :c:func:`PySys_GetOptionalAttr`, and :c:func:`PySys_GetOptionalAttrString` functions as replacements for :c:func:`PySys_GetObject`. diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 61cdb93d1d5354..2d4e675219b2fa 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -303,8 +303,6 @@ PyAPI_FUNC(void) PyObject_Dump(PyObject *); Py_DEPRECATED(3.15) PyAPI_FUNC(PyObject*) _PyObject_GetAttrId(PyObject *, _Py_Identifier *); PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *); -PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *); -PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *); PyAPI_FUNC(void) PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *); diff --git a/Include/object.h b/Include/object.h index 3fb28035a50547..91de54ff6696d9 100644 --- a/Include/object.h +++ b/Include/object.h @@ -856,6 +856,9 @@ PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type); #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15) PyAPI_FUNC(PyObject *) PyType_GetModuleByToken(PyTypeObject *type, const void *token); + +PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *); +PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *); #endif #ifdef __cplusplus diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 67572ab1ba268d..02a6748313992e 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -305,6 +305,21 @@ def test_pyobject_dump(self): output = self.pyobject_dump(NULL) self.assertRegex(output, r'') + def test_pyobject_callfinalizer(self): + # Test PyObject_CallFinalizer() + pyobject_callfinalizer = _testlimitedcapi.pyobject_callfinalizer + + class Finalizer: + def __init__(self): + self.finalized = False + def __del__(self): + self.finalized = True + + obj = Finalizer() + self.assertFalse(obj.finalized) + pyobject_callfinalizer(obj) + self.assertTrue(obj.finalized) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 28f5dd11130c70..df2197d960be1c 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -550,6 +550,8 @@ def test_windows_feature_macros(self): "PyObject_AsWriteBuffer", "PyObject_Bytes", "PyObject_Call", + "PyObject_CallFinalizer", + "PyObject_CallFinalizerFromDealloc", "PyObject_CallFunction", "PyObject_CallFunctionObjArgs", "PyObject_CallMethod", diff --git a/Misc/NEWS.d/next/C_API/2026-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst b/Misc/NEWS.d/next/C_API/2026-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst new file mode 100644 index 00000000000000..a02df88457c4c3 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst @@ -0,0 +1,3 @@ +Add :c:func:`PyObject_CallFinalizer` and +:c:func:`PyObject_CallFinalizerFromDealloc` functions to the limited C API. +Patch by Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 63fd83868b644f..3c0553462c9d31 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2668,7 +2668,6 @@ added = '3.15' # PEP 757 import/export API. - [function.PyLong_GetNativeLayout] added = '3.15' [function.PyLong_Export] @@ -2692,3 +2691,8 @@ # Note: The `_reserved` member of this struct is for interal use only. # (The definition of 'full-abi' was clarified when this entry was added.) struct_abi_kind = 'full-abi' + +[function.PyObject_CallFinalizer] + added = '3.15' +[function.PyObject_CallFinalizerFromDealloc] + added = '3.15' diff --git a/Modules/_testlimitedcapi/object.c b/Modules/_testlimitedcapi/object.c index da6fe3e4efa34c..8fa83fd38767fa 100644 --- a/Modules/_testlimitedcapi/object.c +++ b/Modules/_testlimitedcapi/object.c @@ -1,7 +1,7 @@ -// Need limited C API version 3.13 for Py_GetConstant() +// Need limited C API version 3.15 for PyObject_CallFinalizer() #include "pyconfig.h" // Py_GIL_DISABLED #if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) -# define Py_LIMITED_API 0x030d0000 +# define Py_LIMITED_API 0x030f0000 #endif #include "parts.h" @@ -62,19 +62,25 @@ test_constants(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) Py_RETURN_NONE; } + +static PyObject * +pyobject_callfinalizer(PyObject *Py_UNUSED(module), PyObject *obj) +{ + PyObject_CallFinalizer(obj); + Py_RETURN_NONE; +} + + static PyMethodDef test_methods[] = { {"get_constant", get_constant, METH_VARARGS}, {"get_constant_borrowed", get_constant_borrowed, METH_VARARGS}, {"test_constants", test_constants, METH_NOARGS}, + {"pyobject_callfinalizer", pyobject_callfinalizer, METH_O}, {NULL}, }; int _PyTestLimitedCAPI_Init_Object(PyObject *m) { - if (PyModule_AddFunctions(m, test_methods) < 0) { - return -1; - } - - return 0; + return PyModule_AddFunctions(m, test_methods); } diff --git a/PC/python3dll.c b/PC/python3dll.c index b23bc2b8f4382f..4c1c4a3170a77f 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -487,6 +487,8 @@ EXPORT_FUNC(PyObject_AsReadBuffer) EXPORT_FUNC(PyObject_AsWriteBuffer) EXPORT_FUNC(PyObject_Bytes) EXPORT_FUNC(PyObject_Call) +EXPORT_FUNC(PyObject_CallFinalizer) +EXPORT_FUNC(PyObject_CallFinalizerFromDealloc) EXPORT_FUNC(PyObject_CallFunction) EXPORT_FUNC(PyObject_CallFunctionObjArgs) EXPORT_FUNC(PyObject_CallMethod) From 2b32466f833dc47acaaef24a300cd5ddc89076b3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 20 Mar 2026 11:50:49 +0100 Subject: [PATCH 2/2] Remove PyObject_CallFinalizer() --- Doc/data/stable_abi.dat | 1 - Doc/whatsnew/3.15.rst | 4 ++-- Include/cpython/object.h | 1 + Include/object.h | 2 -- Lib/test/test_capi/test_object.py | 15 -------------- Lib/test/test_stable_abi_ctypes.py | 1 - ...-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst | 3 +-- Misc/stable_abi.toml | 2 -- Modules/_testlimitedcapi/object.c | 20 +++++++------------ PC/python3dll.c | 1 - 10 files changed, 11 insertions(+), 39 deletions(-) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index df603941ac1562..54c0100092a579 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -563,7 +563,6 @@ func,PyObject_ASCII,3.2,, func,PyObject_AsFileDescriptor,3.2,, func,PyObject_Bytes,3.2,, func,PyObject_Call,3.2,, -func,PyObject_CallFinalizer,3.15,, func,PyObject_CallFinalizerFromDealloc,3.15,, func,PyObject_CallFunction,3.2,, func,PyObject_CallFunctionObjArgs,3.2,, diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index a995c166917d68..783d11e13a9f9b 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1662,8 +1662,8 @@ New features (Contributed by Victor Stinner in :gh:`141510`.) -* Add :c:func:`PyObject_CallFinalizer` and - :c:func:`PyObject_CallFinalizerFromDealloc` functions to the limited C API. +* Add :c:func:`PyObject_CallFinalizerFromDealloc` function to the limited C + API. (Contributed by Victor Stinner in :gh:`146063`.) * Add :c:func:`PySys_GetAttr`, :c:func:`PySys_GetAttrString`, diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 2d4e675219b2fa..1eb0508396d26e 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -303,6 +303,7 @@ PyAPI_FUNC(void) PyObject_Dump(PyObject *); Py_DEPRECATED(3.15) PyAPI_FUNC(PyObject*) _PyObject_GetAttrId(PyObject *, _Py_Identifier *); PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *); +PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *); PyAPI_FUNC(void) PyUnstable_Object_ClearWeakRefsNoCallbacks(PyObject *); diff --git a/Include/object.h b/Include/object.h index 91de54ff6696d9..2976fadb8d278d 100644 --- a/Include/object.h +++ b/Include/object.h @@ -856,8 +856,6 @@ PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type); #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15) PyAPI_FUNC(PyObject *) PyType_GetModuleByToken(PyTypeObject *type, const void *token); - -PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *); PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *); #endif diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 02a6748313992e..67572ab1ba268d 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -305,21 +305,6 @@ def test_pyobject_dump(self): output = self.pyobject_dump(NULL) self.assertRegex(output, r'') - def test_pyobject_callfinalizer(self): - # Test PyObject_CallFinalizer() - pyobject_callfinalizer = _testlimitedcapi.pyobject_callfinalizer - - class Finalizer: - def __init__(self): - self.finalized = False - def __del__(self): - self.finalized = True - - obj = Finalizer() - self.assertFalse(obj.finalized) - pyobject_callfinalizer(obj) - self.assertTrue(obj.finalized) - if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index df2197d960be1c..94f34bbf4c575b 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -550,7 +550,6 @@ def test_windows_feature_macros(self): "PyObject_AsWriteBuffer", "PyObject_Bytes", "PyObject_Call", - "PyObject_CallFinalizer", "PyObject_CallFinalizerFromDealloc", "PyObject_CallFunction", "PyObject_CallFunctionObjArgs", diff --git a/Misc/NEWS.d/next/C_API/2026-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst b/Misc/NEWS.d/next/C_API/2026-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst index a02df88457c4c3..e20e11a754f694 100644 --- a/Misc/NEWS.d/next/C_API/2026-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst +++ b/Misc/NEWS.d/next/C_API/2026-03-19-15-28-14.gh-issue-146063.Sc-1RU.rst @@ -1,3 +1,2 @@ -Add :c:func:`PyObject_CallFinalizer` and -:c:func:`PyObject_CallFinalizerFromDealloc` functions to the limited C API. +Add :c:func:`PyObject_CallFinalizerFromDealloc` function to the limited C API. Patch by Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 3c0553462c9d31..dfc1b3ec84e209 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2692,7 +2692,5 @@ # (The definition of 'full-abi' was clarified when this entry was added.) struct_abi_kind = 'full-abi' -[function.PyObject_CallFinalizer] - added = '3.15' [function.PyObject_CallFinalizerFromDealloc] added = '3.15' diff --git a/Modules/_testlimitedcapi/object.c b/Modules/_testlimitedcapi/object.c index 8fa83fd38767fa..da6fe3e4efa34c 100644 --- a/Modules/_testlimitedcapi/object.c +++ b/Modules/_testlimitedcapi/object.c @@ -1,7 +1,7 @@ -// Need limited C API version 3.15 for PyObject_CallFinalizer() +// Need limited C API version 3.13 for Py_GetConstant() #include "pyconfig.h" // Py_GIL_DISABLED #if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) -# define Py_LIMITED_API 0x030f0000 +# define Py_LIMITED_API 0x030d0000 #endif #include "parts.h" @@ -62,25 +62,19 @@ test_constants(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) Py_RETURN_NONE; } - -static PyObject * -pyobject_callfinalizer(PyObject *Py_UNUSED(module), PyObject *obj) -{ - PyObject_CallFinalizer(obj); - Py_RETURN_NONE; -} - - static PyMethodDef test_methods[] = { {"get_constant", get_constant, METH_VARARGS}, {"get_constant_borrowed", get_constant_borrowed, METH_VARARGS}, {"test_constants", test_constants, METH_NOARGS}, - {"pyobject_callfinalizer", pyobject_callfinalizer, METH_O}, {NULL}, }; int _PyTestLimitedCAPI_Init_Object(PyObject *m) { - return PyModule_AddFunctions(m, test_methods); + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + + return 0; } diff --git a/PC/python3dll.c b/PC/python3dll.c index 4c1c4a3170a77f..e44cb906ffde1b 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -487,7 +487,6 @@ EXPORT_FUNC(PyObject_AsReadBuffer) EXPORT_FUNC(PyObject_AsWriteBuffer) EXPORT_FUNC(PyObject_Bytes) EXPORT_FUNC(PyObject_Call) -EXPORT_FUNC(PyObject_CallFinalizer) EXPORT_FUNC(PyObject_CallFinalizerFromDealloc) EXPORT_FUNC(PyObject_CallFunction) EXPORT_FUNC(PyObject_CallFunctionObjArgs)