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

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.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
In the limited C API version 3.13, getting ``Py_None``, ``Py_False``,
2+
``Py_True``, ``Py_Ellipsis`` and ``Py_NotImplemented`` singletons is now
3+
implemented as function calls at the stable ABI level to hide implementation
4+
details. Getting these constants still return borrowed references. Patch by
5+
Victor Stinner.

Misc/stable_abi.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2500,3 +2500,7 @@
25002500
added = '3.13'
25012501
[function.PyType_GetModuleName]
25022502
added = '3.13'
2503+
[function.Py_GetConstant]
2504+
added = '3.13'
2505+
[function.Py_GetConstantBorrowed]
2506+
added = '3.13'

Modules/Setup.stdlib.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
164164
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
165165
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c
166-
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c
166+
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c
167167
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
168168
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
169169

Modules/_testlimitedcapi.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ PyInit__testlimitedcapi(void)
5353
if (_PyTestLimitedCAPI_Init_Long(mod) < 0) {
5454
return NULL;
5555
}
56+
if (_PyTestLimitedCAPI_Init_Object(mod) < 0) {
57+
return NULL;
58+
}
5659
if (_PyTestLimitedCAPI_Init_PyOS(mod) < 0) {
5760
return NULL;
5861
}

Modules/_testlimitedcapi/object.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Need limited C API version 3.13 for Py_GetConstant()
2+
#include "pyconfig.h" // Py_GIL_DISABLED
3+
#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API )
4+
# define Py_LIMITED_API 0x030d0000
5+
#endif
6+
7+
#include "parts.h"
8+
#include "util.h"
9+
10+
11+
/* Test Py_GetConstant() */
12+
static PyObject *
13+
get_constant(PyObject *Py_UNUSED(module), PyObject *args)
14+
{
15+
int constant_id;
16+
if (!PyArg_ParseTuple(args, "i", &constant_id)) {
17+
return NULL;
18+
}
19+
20+
PyObject *obj = Py_GetConstant(constant_id);
21+
if (obj == NULL) {
22+
assert(PyErr_Occurred());
23+
return NULL;
24+
}
25+
return obj;
26+
}
27+
28+
29+
/* Test Py_GetConstantBorrowed() */
30+
static PyObject *
31+
get_constant_borrowed(PyObject *Py_UNUSED(module), PyObject *args)
32+
{
33+
int constant_id;
34+
if (!PyArg_ParseTuple(args, "i", &constant_id)) {
35+
return NULL;
36+
}
37+
38+
PyObject *obj = Py_GetConstantBorrowed(constant_id);
39+
if (obj == NULL) {
40+
assert(PyErr_Occurred());
41+
return NULL;
42+
}
43+
return Py_NewRef(obj);
44+
}
45+
46+
47+
/* Test constants */
48+
static PyObject *
49+
test_constants(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
50+
{
51+
// Test that implementation of constants in the limited C API:
52+
// check that the C code compiles.
53+
//
54+
// Test also that constants and Py_GetConstant() return the same
55+
// objects.
56+
assert(Py_None == Py_GetConstant(Py_CONSTANT_NONE));
57+
assert(Py_False == Py_GetConstant(Py_CONSTANT_FALSE));
58+
assert(Py_True == Py_GetConstant(Py_CONSTANT_TRUE));
59+
assert(Py_Ellipsis == Py_GetConstant(Py_CONSTANT_ELLIPSIS));
60+
assert(Py_NotImplemented == Py_GetConstant(Py_CONSTANT_NOT_IMPLEMENTED));
61+
// Other constants are tested in test_capi.test_object
62+
Py_RETURN_NONE;
63+
}
64+
65+
static PyMethodDef test_methods[] = {
66+
{"get_constant", get_constant, METH_VARARGS},
67+
{"get_constant_borrowed", get_constant_borrowed, METH_VARARGS},
68+
{"test_constants", test_constants, METH_NOARGS},
69+
{NULL},
70+
};
71+
72+
int
73+
_PyTestLimitedCAPI_Init_Object(PyObject *m)
74+
{
75+
if (PyModule_AddFunctions(m, test_methods) < 0) {
76+
return -1;
77+
}
78+
79+
return 0;
80+
}

Modules/_testlimitedcapi/parts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ int _PyTestLimitedCAPI_Init_Complex(PyObject *module);
2929
int _PyTestLimitedCAPI_Init_Dict(PyObject *module);
3030
int _PyTestLimitedCAPI_Init_Float(PyObject *module);
3131
int _PyTestLimitedCAPI_Init_HeaptypeRelative(PyObject *module);
32+
int _PyTestLimitedCAPI_Init_Object(PyObject *module);
3233
int _PyTestLimitedCAPI_Init_List(PyObject *module);
3334
int _PyTestLimitedCAPI_Init_Long(PyObject *module);
3435
int _PyTestLimitedCAPI_Init_PyOS(PyObject *module);

0 commit comments

Comments
 (0)