diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 25629b4da053da8..ef0d64f15b473e2 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -827,6 +827,7 @@ function,Py_EncodeLocale,3.7,, function,Py_EndInterpreter,3.2,, function,Py_EnterRecursiveCall,3.9,, function,Py_Exit,3.2,, +var,Py_False,3.13,, function,Py_FatalError,3.2,, var,Py_FileSystemDefaultEncodeErrors,3.10,, var,Py_FileSystemDefaultEncoding,3.2,, @@ -861,9 +862,11 @@ function,Py_Main,3.2,, function,Py_MakePendingCalls,3.2,, function,Py_NewInterpreter,3.2,, function,Py_NewRef,3.10,, +var,Py_None,3.13,, function,Py_ReprEnter,3.2,, function,Py_ReprLeave,3.2,, function,Py_SetRecursionLimit,3.2,, +var,Py_True,3.13,, type,Py_UCS4,3.2,, macro,Py_UNBLOCK_THREADS,3.2,, var,Py_UTF8Mode,3.8,, diff --git a/Include/boolobject.h b/Include/boolobject.h index 19aef5b1b87c6ae..2a739812fa278bd 100644 --- a/Include/boolobject.h +++ b/Include/boolobject.h @@ -17,9 +17,15 @@ extern "C" { PyAPI_DATA(PyLongObject) _Py_FalseStruct; PyAPI_DATA(PyLongObject) _Py_TrueStruct; -/* Use these macros */ +// Export symbols in the stable ABI +PyAPI_DATA(PyLongObject*) Py_False; +PyAPI_DATA(PyLongObject*) Py_True; + +#ifndef Py_LIMITED_API +// Implement Py_False and Py_True as macros in the non-limited C API #define Py_False _PyObject_CAST(&_Py_FalseStruct) #define Py_True _PyObject_CAST(&_Py_TrueStruct) +#endif // Test if an object is the True singleton, the same as "x is True" in Python. PyAPI_FUNC(int) Py_IsTrue(PyObject *x); diff --git a/Include/object.h b/Include/object.h index 05187fe5dc4f20d..71af3cfb08b6c90 100644 --- a/Include/object.h +++ b/Include/object.h @@ -1070,7 +1070,14 @@ _Py_NoneStruct is an object of undefined type which can be used in contexts where NULL (nil) is not suitable (since NULL often means 'error'). */ PyAPI_DATA(PyObject) _Py_NoneStruct; /* Don't use this directly */ + +// Export the symbol in the stable ABI +PyAPI_DATA(PyObject*) Py_None; + +#ifndef Py_LIMITED_API +// Implement Py_None as a macro in the non-limited C API #define Py_None (&_Py_NoneStruct) +#endif // Test if an object is the None singleton, the same as "x is None" in Python. PyAPI_FUNC(int) Py_IsNone(PyObject *x); diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 8bd373976426ef3..abdafcbda8cca10 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -40,6 +40,16 @@ def test_windows_feature_macros(self): with self.subTest(name): self.assertEqual(feature_macros[name], value) + def test_constants(self): + process = ctypes_test.pythonapi + def get_object(name): + return ctypes_test.py_object.in_dll(process, name).value + + self.assertIs(get_object("Py_None"), None) + self.assertIs(get_object("Py_False"), False) + self.assertIs(get_object("Py_True"), True) + + SYMBOL_NAMES = ( "PyAIter_Check", @@ -844,6 +854,7 @@ def test_windows_feature_macros(self): "Py_EndInterpreter", "Py_EnterRecursiveCall", "Py_Exit", + "Py_False", "Py_FatalError", "Py_FileSystemDefaultEncodeErrors", "Py_FileSystemDefaultEncoding", @@ -879,12 +890,14 @@ def test_windows_feature_macros(self): "Py_MakePendingCalls", "Py_NewInterpreter", "Py_NewRef", + "Py_None", "Py_ReprEnter", "Py_ReprLeave", "Py_SetPath", "Py_SetProgramName", "Py_SetPythonHome", "Py_SetRecursionLimit", + "Py_True", "Py_UTF8Mode", "Py_VaBuildValue", "Py_Version", diff --git a/Misc/NEWS.d/next/C API/2024-02-21-10-54-27.gh-issue-115754.RSL-q0.rst b/Misc/NEWS.d/next/C API/2024-02-21-10-54-27.gh-issue-115754.RSL-q0.rst new file mode 100644 index 000000000000000..f10a0d2a08f0eda --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-02-21-10-54-27.gh-issue-115754.RSL-q0.rst @@ -0,0 +1,6 @@ +In the limited C API and the stable ABI, implement ``Py_None``, ``Py_False`` +and ``Py_True`` constants are symbols, rather than implementing them as +macros. So they can be loaded by dlopen/dlsym in an embedded in Python, +rather than having to reimplement these macros manually. In the non-limited +C API, these constants are still implemented as macros. Patch by Victor +Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index ca7cf02961571ee..154d5e319d4513c 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2496,3 +2496,9 @@ [typedef.PyCFunctionFastWithKeywords] added = '3.13' # "abi-only" since 3.10. (Same story as PyCFunctionFast.) +[data.Py_False] + added = '3.13' +[data.Py_True] + added = '3.13' +[data.Py_None] + added = '3.13' diff --git a/Objects/boolobject.c b/Objects/boolobject.c index fb48dcbeca78503..c05f89812165476 100644 --- a/Objects/boolobject.c +++ b/Objects/boolobject.c @@ -225,3 +225,10 @@ struct _longobject _Py_TrueStruct = { { 1 } } }; + +// Stable ABI: export symbols + +#undef Py_False +#undef Py_True +PyLongObject *Py_False = _Py_CAST(PyLongObject*, &_Py_FalseStruct); +PyLongObject *Py_True = _Py_CAST(PyLongObject*, &_Py_TrueStruct); diff --git a/Objects/object.c b/Objects/object.c index 23eab8288a41e8b..3fdcb5320d4f39b 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2970,3 +2970,8 @@ _Py_SetRefcnt(PyObject *ob, Py_ssize_t refcnt) { Py_SET_REFCNT(ob, refcnt); } + +#undef Py_None + +// Export the symbol in the stable ABI +PyObject *Py_None = &_Py_NoneStruct; diff --git a/PC/python3dll.c b/PC/python3dll.c index aa6bfe2c4022db0..ce878caaa9838f0 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -775,10 +775,13 @@ EXPORT_DATA(_Py_TrueStruct) EXPORT_DATA(_PyWeakref_CallableProxyType) EXPORT_DATA(_PyWeakref_ProxyType) EXPORT_DATA(_PyWeakref_RefType) +EXPORT_DATA(Py_False) EXPORT_DATA(Py_FileSystemDefaultEncodeErrors) EXPORT_DATA(Py_FileSystemDefaultEncoding) EXPORT_DATA(Py_GenericAliasType) EXPORT_DATA(Py_HasFileSystemDefaultEncoding) +EXPORT_DATA(Py_None) +EXPORT_DATA(Py_True) EXPORT_DATA(Py_UTF8Mode) EXPORT_DATA(Py_Version) EXPORT_DATA(PyBaseObject_Type) diff --git a/Tools/build/stable_abi.py b/Tools/build/stable_abi.py index 83146622c74f941..2c3185cc006e24c 100644 --- a/Tools/build/stable_abi.py +++ b/Tools/build/stable_abi.py @@ -309,6 +309,16 @@ def test_windows_feature_macros(self): with self.subTest(name): self.assertEqual(feature_macros[name], value) + def test_constants(self): + process = ctypes_test.pythonapi + def get_object(name): + return ctypes_test.py_object.in_dll(process, name).value + + self.assertIs(get_object("Py_None"), None) + self.assertIs(get_object("Py_False"), False) + self.assertIs(get_object("Py_True"), True) + + SYMBOL_NAMES = ( ''')) items = manifest.select(