Skip to content

Commit 8bea6c4

Browse files
authored
gh-115754: Add Py_GetConstant() function (#116883)
Add Py_GetConstant() and Py_GetConstantBorrowed() functions. In the limited C API version 3.13, getting Py_None, Py_False, Py_True, Py_Ellipsis and Py_NotImplemented singletons is now implemented as function calls at the stable ABI level to hide implementation details. Getting these constants still return borrowed references. Add _testlimitedcapi/object.c and test_capi/test_object.py to test Py_GetConstant() and Py_GetConstantBorrowed() functions.
1 parent 5a76d1b commit 8bea6c4

File tree

22 files changed

+312
-6
lines changed

22 files changed

+312
-6
lines changed

Doc/c-api/object.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,55 @@ Object Protocol
66
===============
77

88

9+
.. c:function:: PyObject* Py_GetConstant(unsigned int constant_id)
10+
11+
Get a :term:`strong reference` to a constant.
12+
13+
Set an exception and return ``NULL`` if *constant_id* is invalid.
14+
15+
*constant_id* must be one of these constant identifiers:
16+
17+
.. c:namespace:: NULL
18+
19+
======================================== ===== =========================
20+
Constant Identifier Value Returned object
21+
======================================== ===== =========================
22+
.. c:macro:: Py_CONSTANT_NONE ``0`` :py:data:`None`
23+
.. c:macro:: Py_CONSTANT_FALSE ``1`` :py:data:`False`
24+
.. c:macro:: Py_CONSTANT_TRUE ``2`` :py:data:`True`
25+
.. c:macro:: Py_CONSTANT_ELLIPSIS ``3`` :py:data:`Ellipsis`
26+
.. c:macro:: Py_CONSTANT_NOT_IMPLEMENTED ``4`` :py:data:`NotImplemented`
27+
.. c:macro:: Py_CONSTANT_ZERO ``5`` ``0``
28+
.. c:macro:: Py_CONSTANT_ONE ``6`` ``1``
29+
.. c:macro:: Py_CONSTANT_EMPTY_STR ``7`` ``''``
30+
.. c:macro:: Py_CONSTANT_EMPTY_BYTES ``8`` ``b''``
31+
.. c:macro:: Py_CONSTANT_EMPTY_TUPLE ``9`` ``()``
32+
======================================== ===== =========================
33+
34+
Numeric values are only given for projects which cannot use the constant
35+
identifiers.
36+
37+
38+
.. versionadded:: 3.13
39+
40+
.. impl-detail::
41+
42+
In CPython, all of these constants are :term:`immortal`.
43+
44+
45+
.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id)
46+
47+
Similar to :c:func:`Py_GetConstant`, but return a :term:`borrowed
48+
reference`.
49+
50+
This function is primarily intended for backwards compatibility:
51+
using :c:func:`Py_GetConstant` is recommended for new code.
52+
53+
The reference is borrowed from the interpreter, and is valid until the
54+
interpreter finalization.
55+
.. versionadded:: 3.13
56+
57+
958
.. c:var:: PyObject* Py_NotImplemented
1059
1160
The ``NotImplemented`` singleton, used to signal that an operation is

Doc/data/stable_abi.dat

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.13.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,11 @@ New Features
17311731
more information.
17321732
(Contributed by Victor Stinner in :gh:`111696`.)
17331733

1734+
* Add :c:func:`Py_GetConstant` and :c:func:`Py_GetConstantBorrowed` functions
1735+
to get constants. For example, ``Py_GetConstant(Py_CONSTANT_ZERO)`` returns a
1736+
:term:`strong reference` to the constant zero.
1737+
(Contributed by Victor Stinner in :gh:`115754`.)
1738+
17341739

17351740
Porting to Python 3.13
17361741
----------------------

Include/boolobject.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ PyAPI_DATA(PyLongObject) _Py_FalseStruct;
1818
PyAPI_DATA(PyLongObject) _Py_TrueStruct;
1919

2020
/* Use these macros */
21-
#define Py_False _PyObject_CAST(&_Py_FalseStruct)
22-
#define Py_True _PyObject_CAST(&_Py_TrueStruct)
21+
#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000
22+
# define Py_False Py_GetConstantBorrowed(Py_CONSTANT_FALSE)
23+
# define Py_True Py_GetConstantBorrowed(Py_CONSTANT_TRUE)
24+
#else
25+
# define Py_False _PyObject_CAST(&_Py_FalseStruct)
26+
# define Py_True _PyObject_CAST(&_Py_TrueStruct)
27+
#endif
2328

2429
// Test if an object is the True singleton, the same as "x is True" in Python.
2530
PyAPI_FUNC(int) Py_IsTrue(PyObject *x);

Include/internal/pycore_object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,8 @@ PyAPI_DATA(PyTypeObject) _PyNotImplemented_Type;
716716
// Export for the stable ABI.
717717
PyAPI_DATA(int) _Py_SwappedOp[];
718718

719+
extern void _Py_GetConstant_Init(void);
720+
719721
#ifdef __cplusplus
720722
}
721723
#endif

Include/object.h

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,12 +1068,34 @@ static inline PyObject* _Py_XNewRef(PyObject *obj)
10681068
#endif
10691069

10701070

1071+
#define Py_CONSTANT_NONE 0
1072+
#define Py_CONSTANT_FALSE 1
1073+
#define Py_CONSTANT_TRUE 2
1074+
#define Py_CONSTANT_ELLIPSIS 3
1075+
#define Py_CONSTANT_NOT_IMPLEMENTED 4
1076+
#define Py_CONSTANT_ZERO 5
1077+
#define Py_CONSTANT_ONE 6
1078+
#define Py_CONSTANT_EMPTY_STR 7
1079+
#define Py_CONSTANT_EMPTY_BYTES 8
1080+
#define Py_CONSTANT_EMPTY_TUPLE 9
1081+
1082+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
1083+
PyAPI_FUNC(PyObject*) Py_GetConstant(unsigned int constant_id);
1084+
PyAPI_FUNC(PyObject*) Py_GetConstantBorrowed(unsigned int constant_id);
1085+
#endif
1086+
1087+
10711088
/*
10721089
_Py_NoneStruct is an object of undefined type which can be used in contexts
10731090
where NULL (nil) is not suitable (since NULL often means 'error').
10741091
*/
10751092
PyAPI_DATA(PyObject) _Py_NoneStruct; /* Don't use this directly */
1076-
#define Py_None (&_Py_NoneStruct)
1093+
1094+
#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000
1095+
# define Py_None Py_GetConstantBorrowed(Py_CONSTANT_NONE)
1096+
#else
1097+
# define Py_None (&_Py_NoneStruct)
1098+
#endif
10771099

10781100
// Test if an object is the None singleton, the same as "x is None" in Python.
10791101
PyAPI_FUNC(int) Py_IsNone(PyObject *x);
@@ -1087,7 +1109,12 @@ Py_NotImplemented is a singleton used to signal that an operation is
10871109
not implemented for a given type combination.
10881110
*/
10891111
PyAPI_DATA(PyObject) _Py_NotImplementedStruct; /* Don't use this directly */
1090-
#define Py_NotImplemented (&_Py_NotImplementedStruct)
1112+
1113+
#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000
1114+
# define Py_NotImplemented Py_GetConstantBorrowed(Py_CONSTANT_NOT_IMPLEMENTED)
1115+
#else
1116+
# define Py_NotImplemented (&_Py_NotImplementedStruct)
1117+
#endif
10911118

10921119
/* Macro for returning Py_NotImplemented from a function */
10931120
#define Py_RETURN_NOTIMPLEMENTED return Py_NotImplemented

Include/sliceobject.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ extern "C" {
88

99
PyAPI_DATA(PyObject) _Py_EllipsisObject; /* Don't use this directly */
1010

11-
#define Py_Ellipsis (&_Py_EllipsisObject)
11+
#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000
12+
# define Py_Ellipsis Py_GetConstantBorrowed(Py_CONSTANT_ELLIPSIS)
13+
#else
14+
# define Py_Ellipsis (&_Py_EllipsisObject)
15+
#endif
1216

1317
/* Slice object interface */
1418

Lib/test/test_capi/test_object.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import enum
2+
import unittest
3+
from test.support import import_helper
4+
5+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
6+
7+
8+
class Constant(enum.IntEnum):
9+
Py_CONSTANT_NONE = 0
10+
Py_CONSTANT_FALSE = 1
11+
Py_CONSTANT_TRUE = 2
12+
Py_CONSTANT_ELLIPSIS = 3
13+
Py_CONSTANT_NOT_IMPLEMENTED = 4
14+
Py_CONSTANT_ZERO = 5
15+
Py_CONSTANT_ONE = 6
16+
Py_CONSTANT_EMPTY_STR = 7
17+
Py_CONSTANT_EMPTY_BYTES = 8
18+
Py_CONSTANT_EMPTY_TUPLE = 9
19+
20+
INVALID_CONSTANT = Py_CONSTANT_EMPTY_TUPLE + 1
21+
22+
23+
class CAPITest(unittest.TestCase):
24+
def check_get_constant(self, get_constant):
25+
self.assertIs(get_constant(Constant.Py_CONSTANT_NONE), None)
26+
self.assertIs(get_constant(Constant.Py_CONSTANT_FALSE), False)
27+
self.assertIs(get_constant(Constant.Py_CONSTANT_TRUE), True)
28+
self.assertIs(get_constant(Constant.Py_CONSTANT_ELLIPSIS), Ellipsis)
29+
self.assertIs(get_constant(Constant.Py_CONSTANT_NOT_IMPLEMENTED), NotImplemented)
30+
31+
for constant_id, constant_type, value in (
32+
(Constant.Py_CONSTANT_ZERO, int, 0),
33+
(Constant.Py_CONSTANT_ONE, int, 1),
34+
(Constant.Py_CONSTANT_EMPTY_STR, str, ""),
35+
(Constant.Py_CONSTANT_EMPTY_BYTES, bytes, b""),
36+
(Constant.Py_CONSTANT_EMPTY_TUPLE, tuple, ()),
37+
):
38+
with self.subTest(constant_id=constant_id):
39+
obj = get_constant(constant_id)
40+
self.assertEqual(type(obj), constant_type, obj)
41+
self.assertEqual(obj, value)
42+
43+
with self.assertRaises(SystemError):
44+
get_constant(Constant.INVALID_CONSTANT)
45+
46+
def test_get_constant(self):
47+
self.check_get_constant(_testlimitedcapi.get_constant)
48+
49+
def test_get_constant_borrowed(self):
50+
self.check_get_constant(_testlimitedcapi.get_constant_borrowed)
51+
52+
53+
if __name__ == "__main__":
54+
unittest.main()

Lib/test/test_stable_abi_ctypes.py

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`Py_GetConstant` and :c:func:`Py_GetConstantBorrowed` functions to
2+
get constants. For example, ``Py_GetConstant(Py_CONSTANT_ZERO)`` returns a
3+
:term:`strong reference` to the constant zero. Patch by Victor Stinner.

0 commit comments

Comments
 (0)