8000 gh-115754: Add Py_GetConstantRef() function by vstinner · Pull Request #116883 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-115754: Add Py_GetConstantRef() function #116883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,55 @@ Object Protocol
===============


.. c:function:: PyObject* Py_GetConstant(unsigned int constant_id)

Get a :term:`strong reference` to a constant.

Set an exception and return ``NULL`` if *constant_id* is invalid.

*constant_id* must be one of these constant identifiers:

.. c:namespace:: NULL

======================================== ===== =========================
Constant Identifier Value Returned object
======================================== ===== =========================
.. c:macro:: Py_CONSTANT_NONE ``0`` :py:data:`None`
.. c:macro:: Py_CONSTANT_FALSE ``1`` :py:data:`False`
.. c:macro:: Py_CONSTANT_TRUE ``2`` :py:data:`True`
.. c:macro:: Py_CONSTANT_ELLIPSIS ``3`` :py:data:`Ellipsis`
.. c:macro:: Py_CONSTANT_NOT_IMPLEMENTED ``4`` :py:data:`NotImplemented`
.. c:macro:: Py_CONSTANT_ZERO ``5`` ``0``
.. c:macro:: Py_CONSTANT_ONE ``6`` ``1``
.. c:macro:: Py_CONSTANT_EMPTY_STR ``7`` ``''``
.. c:macro:: Py_CONSTANT_EMPTY_BYTES ``8`` ``b''``
.. c:macro:: Py_CONSTANT_EMPTY_TUPLE ``9`` ``()``
======================================== ===== =========================

Numeric values are only given for projects which cannot use the constant
identifiers.


.. versionadded:: 3.13

.. impl-detail::

In CPython, all of these constants are :term:`immortal`.


.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id)

Similar to :c:func:`Py_GetConstant`, but return a :term:`borrowed
reference`.

This function is primarily intended for backwards compatibility:
using :c:func:`Py_GetConstant` is recommended for new code.

The reference is borrowed from the interpreter, and is valid until the
interpreter finalization.
.. versionadded:: 3.13


.. c:var:: PyObject* Py_NotImplemented

The ``NotImplemented`` singleton, used to signal that an operation is
Expand Down
2 changes: 2 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,11 @@ New Features
more information.
(Contributed by Victor Stinner in :gh:`111696`.)

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


Porting to Python 3.13
----------------------
Expand Down
9 changes: 7 additions & 2 deletions Include/boolobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ PyAPI_DATA(PyLongObject) _Py_FalseStruct;
PyAPI_DATA(PyLongObject) _Py_TrueStruct;

/* Use these macros */
#define Py_False _PyObject_CAST(&_Py_FalseStruct)
#define Py_True _PyObject_CAST(&_Py_TrueStruct)
#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000
# define Py_False Py_GetConstantBorrowed(Py_CONSTANT_FALSE)
# define Py_True Py_GetConstantBorrowed(Py_CONSTANT_TRUE)
#else
# 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);
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_object.h
9E81
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,8 @@ PyAPI_DATA(PyTypeObject) _PyNotImplemented_Type;
// Export for the stable ABI.
PyAPI_DATA(int) _Py_SwappedOp[];

extern void _Py_GetConstant_Init(void);

#ifdef __cplusplus
}
#endif
Expand Down
31 changes: 29 additions & 2 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -1068,12 +1068,34 @@ static inline PyObject* _Py_XNewRef(PyObject *obj)
#endif


#define Py_CONSTANT_NONE 0
#define Py_CONSTANT_FALSE 1
#define Py_CONSTANT_TRUE 2
#define Py_CONSTANT_ELLIPSIS 3
#define Py_CONSTANT_NOT_IMPLEMENTED 4
#define Py_CONSTANT_ZERO 5
#define Py_CONSTANT_ONE 6
#define Py_CONSTANT_EMPTY_STR 7
#define Py_CONSTANT_EMPTY_BYTES 8
#define Py_CONSTANT_EMPTY_TUPLE 9

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
PyAPI_FUNC(PyObject*) Py_GetConstant(unsigned int constant_id);
PyAPI_FUNC(PyObject*) Py_GetConstantBorrowed(unsigned int constant_id);
#endif


/*
_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 */
#define Py_None (&_Py_NoneStruct)

#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000
# define Py_None Py_GetConstantBorrowed(Py_CONSTANT_NONE)
#else
# 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);
Expand All @@ -1087,7 +1109,12 @@ Py_NotImplemented is a singleton used to signal that an operation is
not implemented for a given type combination.
*/
PyAPI_DATA(PyObject) _Py_NotImplementedStruct; /* Don't use this directly */
#define Py_NotImplemented (&_Py_NotImplementedStruct)

#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000
# define Py_NotImplemented Py_GetConstantBorrowed(Py_CONSTANT_NOT_IMPLEMENTED)
#else
# define Py_NotImplemented (&_Py_NotImplementedStruct)
#endif

/* Macro for returning Py_NotImplemented from a function */
#define Py_RETURN_NOTIMPLEMENTED return Py_NotImplemented
Expand Down
6 changes: 5 additions & 1 deletion Include/sliceobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ extern "C" {

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

#define Py_Ellipsis (&_Py_EllipsisObject)
#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030D0000
# define Py_Ellipsis Py_GetConstantBorrowed(Py_CONSTANT_ELLIPSIS)
#else
# define Py_Ellipsis (&_Py_EllipsisObject)
#endif

/* Slice object interface */

Expand Down
54 changes: 54 additions & 0 deletions Lib/test/test_capi/test_object.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import enum
import unittest
from test.support import import_helper

_testlimitedcapi = import_helper.import_module('_testlimitedcapi')


class Constant(enum.IntEnum):
Py_CONSTANT_NONE = 0
Py_CONSTANT_FALSE = 1
Py_CONSTANT_TRUE = 2
Py_CONSTANT_ELLIPSIS = 3
Py_CONSTANT_NOT_IMPLEMENTED = 4
Py_CONSTANT_ZERO = 5
Py_CONSTANT_ONE = 6
Py_CONSTANT_EMPTY_STR = 7
Py_CONSTANT_EMPTY_BYTES = 8
Py_CONSTANT_EMPTY_TUPLE = 9

INVALID_CONSTANT = Py_CONSTANT_EMPTY_TUPLE + 1


class CAPITest(unittest.TestCase):
def check_get_constant(self, get_constant):
self.assertIs(get_constant(Constant.Py_CONSTANT_NONE), None)
self.assertIs(get_constant(Constant.Py_CONSTANT_FALSE), False)
self.assertIs(get_constant(Constant.Py_CONSTANT_TRUE), True)
self.assertIs(get_constant(Constant.Py_CONSTANT_ELLIPSIS), Ellipsis)
self.assertIs(get_constant(Constant.Py_CONSTANT_NOT_IMPLEMENTED), NotImplemented)

for constant_id, constant_type, value in (
(Constant.Py_CONSTANT_ZERO, int, 0),
(Constant.Py_CONSTANT_ONE, int, 1),
(Constant.Py_CONSTANT_EMPTY_STR, str, ""),
(Constant.Py_CONSTANT_EMPTY_BYTES, bytes, b""),
(Constant.Py_CONSTANT_EMPTY_TUPLE, tuple, ()),
):
with self.subTest(constant_id=constant_id):
obj = get_constant(constant_id)
self.assertEqual(type(obj), constant_type, obj)
self.assertEqual(obj, value)

with self.assertRaises(SystemError):
get_constant(Constant.INVALID_CONSTANT)

def test_get_constant(self):
self.check_get_constant(_testlimitedcapi.get_constant)

def test_get_constant_borrowed(self):
self.check_get_constant(_testlimitedcapi.get_constant_borrowed)


if __name__ == "__main__":
unittest.main()
2 changes: 2 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`Py_GetConstant` and :c:func:`Py_GetConstantBorrowed` functions to
get constants. For example, ``Py_GetConstant(Py_CONSTANT_ZERO)`` returns a
:term:`strong reference` to the constant zero. Patch by Victor Stinner.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
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. Patch by
Victor Stinner.
4 changes: 4 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2500,3 +2500,7 @@
added = '3.13'
[function.PyType_GetModuleName]
added = '3.13'
[function.Py_GetConstant]
added = '3.13'
[function.Py_GetConstantBorrowed]
added = '3.13'
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
@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
@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
@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
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c

Expand Down
3 changes: 3 additions & 0 deletions Modules/_testlimitedcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ PyInit__testlimitedcapi(void)
if (_PyTestLimitedCAPI_Init_Long(mod) < 0) {
return NULL;
}
if (_PyTestLimitedCAPI_Init_Object(mod) < 0) {
return NULL;
}
if (_PyTestLimitedCAPI_Init_PyOS(mod) < 0) {
return NULL;
}
Expand Down
80 changes: 80 additions & 0 deletions Modules/_testlimitedcapi/object.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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 0x030d0000
#endif

#include "parts.h"
#include "util.h"


/* Test Py_GetConstant() */
static PyObject *
get_constant(PyObject *Py_UNUSED(module), PyObject *args)
{
int constant_id;
if (!PyArg_ParseTuple(args, "i", &constant_id)) {
return NULL;
}

PyObject *obj = Py_GetConstant(constant_id);
if (obj == NULL) {
assert(PyErr_Occurred());
return NULL;
}
return obj;
}


/* Test Py_GetConstantBorrowed() */
static PyObject *
get_constant_borrowed(PyObject *Py_UNUSED(module), PyObject *args)
{
int constant_id;
if (!PyArg_ParseTuple(args, "i", &constant_id)) {
return NULL;
}

PyObject *obj = Py_GetConstantBorrowed(constant_id);
if (obj == NULL) {
assert(PyErr_Occurred());
return NULL;
}
return Py_NewRef(obj);
}


/* Test constants */
static PyObject *
test_constants(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
// Test that implementation of constants in the limited C API:
// check that the C code compiles.
//
// Test also that constants and Py_GetConstant() return the same
// objects.
assert(Py_None == Py_GetConstant(Py_CONSTANT_NONE));
assert(Py_False == Py_GetConstant(Py_CONSTANT_FALSE));
assert(Py_True == Py_GetConstant(Py_CONSTANT_TRUE));
assert(Py_Ellipsis == Py_GetConstant(Py_CONSTANT_ELLIPSIS));
assert(Py_NotImplemented == Py_GetConstant(Py_CONSTANT_NOT_IMPLEMENTED));
// Other constants are tested in test_capi.test_object
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},
{NULL},
};

int
_PyTestLimitedCAPI_Init_Object(PyObject *m)
{
if (PyModule_AddFunctions(m, test_methods) < 0) {
return -1;
}

return 0;
}
1 change: 1 addition & 0 deletions Modules/_testlimitedcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ int _PyTestLimitedCAPI_Init_Complex(PyObject *module);
int _PyTestLimitedCAPI_Init_Dict(PyObject *module);
int _PyTestLimitedCAPI_Init_Float(PyObject *module);
int _PyTestLimitedCAPI_Init_HeaptypeRelative(PyObject *module);
int _PyTestLimitedCAPI_Init_Object(PyObject *module);
int _PyTestLimitedCAPI_Init_List(PyObject *module);
int _PyTestLimitedCAPI_Init_Long(PyObject *module);
int _PyTestLimitedCAPI_Init_PyOS(PyObject *module);
Expand Down
Loading
0