8000 gh-115754: Add Py_GetConstant() function · python/cpython@48f94a0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 48f94a0

Browse files
committed
gh-115754: Add Py_GetConstant() function
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 4159644 commit 48f94a0

22 files changed

+295
-6
lines changed

Doc/c-api/object.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,33 @@ 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+
Return ``NULL`` if *constant_id* is invalid.
14+
15+
Constant identifiers:
16+
17+
.. c:macro: Py_NONE_IDX
18+
.. c:macro: Py_FALSE_IDX
19+
.. c:macro: Py_TRUE_IDX
20+
.. c:macro: Py_ELLIPSIS_IDX
21+
.. c:macro: Py_NOT_IMPLEMENTED_IDX
22+
.. c:macro: Py_ZERO_IDX
23+
.. c:macro: Py_ONE_IDX
24+
.. c:macro: Py_EMPTY_STR_IDX
25+
.. c:macro: Py_EMPTY_BYTES_IDX
26+
.. c:macro: Py_EMPTY_TUPLE_IDX
27+
28+
.. versionadded:: 3.13
29+
30+
31+
.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id)
32+
33+
Similar to :c:func:`Py_GetConstant`, but return a :term:`borrowed
34+
reference`.
35+
936
.. c:var:: PyObject* Py_NotImplemented
1037
1138
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,17 @@ New Features
16971697
more information.
16981698
(Contributed by Victor Stinner in :gh:`111696`.)
16991699

1700+
* Add :c:func:`Py_GetConstant` and :c:func:`Py_GetConstantBorrowed` functions
1701+
to get constants. For example, ``Py_GetConstant(Py_ZERO_IDX)`` returns a
1702+
:term:`strong reference` to the constant zero.
1703+
(Contributed by Victor Stinner in :gh:`115754`.)
1704+
1705+
* In the limited C API version 3.13, getting ``Py_None``, ``Py_False``,
1706+
``Py_True``, ``Py_Ellipsis`` and ``Py_NotImplemented`` singletons is now
1707+
implemented as function calls at the stable ABI level to hide implementation
1708+
details. Getting these constants still return borrowed references.
1709+
(Contributed by Victor Stinner in :gh:`115754`.)
1710+
17001711

17011712
Porting to Python 3.13
17021713
----------------------

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_FALSE_IDX)
23+
# define Py_True Py_GetConstantBorrowed(Py_TRUE_IDX)
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
@@ -726,6 +726,8 @@ PyAPI_DATA(PyTypeObject) _PyNotImplemented_Type;
726726
// Export for the stable ABI.
727727
PyAPI_DATA(int) _Py_SwappedOp[];
728728

729+
extern void _Py_GetConstant_Init(void);
730+
729731
#ifdef __cplusplus
730732
}
731733
#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_NONE_IDX 0
1072+
#define Py_FALSE_IDX 1
1073+
#define Py_TRUE_IDX 2
1074+
#define Py_ELLIPSIS_IDX 3
1075+
#define Py_NOT_IMPLEMENTED_IDX 4
1076+
#define Py_ZERO_IDX 5
1077+
#define Py_ONE_IDX 6
1078+
#define Py_EMPTY_STR_IDX 7
1079+
#define Py_EMPTY_BYTES_IDX 8
1080+
#define Py_EMPTY_TUPLE_IDX 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_NONE_IDX)
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_NOT_IMPLEMENTED_IDX)
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_ELLIPSIS_IDX)
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_NONE_IDX = 0
10+
Py_FALSE_IDX = 1
11+
Py_TRUE_IDX = 2
12+
Py_ELLIPSIS_IDX = 3
13+
Py_NOT_IMPLEMENTED_IDX = 4
14+
Py_ZERO_IDX = 5
15+
Py_ONE_IDX = 6
16+
Py_EMPTY_STR_IDX = 7
17+
Py_EMPTY_BYTES_IDX = 8
18+
Py_EMPTY_TUPLE_IDX = 9
19+
20+
INVALID_IDX = Py_EMPTY_TUPLE_IDX + 1
21+
22+
23+
class CAPITest(unittest.TestCase):
24+
def check_get_constant(self, get_constant):
25+
self.assertIs(get_constant(Constant.Py_NONE_IDX), None)
26+
self.assertIs(get_constant(Constant.Py_FALSE_IDX), False)
27+
self.assertIs(get_constant(Constant.Py_TRUE_IDX), True)
28+
self.assertIs(get_constant(Constant.Py_ELLIPSIS_IDX), Ellipsis)
29+
self.assertIs(get_constant(Constant.Py_NOT_IMPLEMENTED_IDX), NotImplemented)
30+
31+
for constant_id, constant_type, value in (
32+
(Constant.Py_ZERO_IDX, int, 0),
33+
(Constant.Py_ONE_IDX, int, 1),
34+
(Constant.Py_EMPTY_STR_IDX, str, ""),
35+
(Constant.Py_EMPTY_BYTES_IDX, bytes, b""),
36+
(Constant.Py_EMPTY_TUPLE_IDX, 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(ValueError):
44+
get_constant(Constant.INVALID_IDX)
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_ZERO_IDX)`` returns a
3+
:term:`strong reference` to the constant zero. Patch by Victor Stinner.

0 commit comments

Comments
 (0)
0