From b16ff9aeefddac76e6965dba3dee65d3fcc39981 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 21 Mar 2024 17:20:06 +0100 Subject: [PATCH] Add Py_GetConstant() and Py_GetConstantBorrowed() (#87) --- docs/api.rst | 9 +++ docs/changelog.rst | 5 ++ pythoncapi_compat.h | 77 ++++++++++++++++++++++++++ tests/test_pythoncapi_compat_cext.c | 86 +++++++++++++++++++++++++++++ 4 files changed, 177 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 162b27b..598c1cb 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -192,6 +192,15 @@ Python 3.12 Not available on PyPy. +.. c:function:: PyObject* Py_GetConstant(unsigned int constant_id) + + See `Py_GetConstant() documentation `__. + +.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id) + + See `Py_GetConstantBorrowed() documentation `__. + + Not supported: * ``PyDict_AddWatcher()``, ``PyDict_Watch()``. diff --git a/docs/changelog.rst b/docs/changelog.rst index 2569314..f869e10 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +* 2024-03-21: Add functions: + + * ``Py_GetConstant()`` + * ``Py_GetConstantBorrowed()`` + * 2024-03-09: Add hash constants: * ``PyHASH_BITS`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 7164b46..6cd3394 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1209,6 +1209,83 @@ static inline int PyTime_PerfCounter(PyTime_t *result) #endif +// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed() +// to Python 3.13.0a6 +#if PY_VERSION_HEX < 0x030D00A6 + +#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 + +static inline PyObject* Py_GetConstant(unsigned int constant_id) +{ + static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL}; + + if (constants[Py_CONSTANT_NONE] == NULL) { + constants[Py_CONSTANT_NONE] = Py_None; + constants[Py_CONSTANT_FALSE] = Py_False; + constants[Py_CONSTANT_TRUE] = Py_True; + constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis; + constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented; + + constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0); + if (constants[Py_CONSTANT_ZERO] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_ONE] = PyLong_FromLong(1); + if (constants[Py_CONSTANT_ONE] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_STR] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0); + if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) { + goto fatal_error; + } + // goto dance to avoid compiler warnings about Py_FatalError() + goto init_done; + +fatal_error: + // This case should never happen + Py_FatalError("Py_GetConstant() failed to get constants"); + } + +init_done: + if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) { + return Py_NewRef(constants[constant_id]); + } + else { + PyErr_BadInternalCall(); + return NULL; + } +} + +static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id) +{ + PyObject *obj = Py_GetConstant(constant_id); + Py_XDECREF(obj); + return obj; +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 4e9fb83..d906f94 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1573,6 +1573,91 @@ test_time(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) #endif +static void +check_get_constant(PyObject* (*get_constant)(unsigned int), int borrowed) +{ +#define CLEAR(var) if (!borrowed) { Py_DECREF(var); } + + PyObject *obj, *expected; + + // Py_CONSTANT_NONE + obj = get_constant(Py_CONSTANT_NONE); + assert(obj == Py_None); + CLEAR(obj); + + // Py_CONSTANT_FALSE + obj = get_constant(Py_CONSTANT_FALSE); + assert(obj = Py_False); + CLEAR(obj); + + // Py_CONSTANT_TRUE + obj = get_constant(Py_CONSTANT_TRUE); + assert(obj == Py_True); + CLEAR(obj); + + // Py_CONSTANT_ELLIPSIS + obj = get_constant(Py_CONSTANT_ELLIPSIS); + assert(obj == Py_Ellipsis); + CLEAR(obj); + + // Py_CONSTANT_NOT_IMPLEMENTED + obj = get_constant(Py_CONSTANT_NOT_IMPLEMENTED); + assert(obj == Py_NotImplemented); + CLEAR(obj); + + // Py_CONSTANT_ZERO + obj = get_constant(Py_CONSTANT_ZERO); + expected = PyLong_FromLong(0); + assert(expected != NULL); + assert(Py_TYPE(obj) == &PyLong_Type); + assert(PyObject_RichCompareBool(obj, expected, Py_EQ) == 1); + CLEAR(obj); + Py_DECREF(expected); + + // Py_CONSTANT_ONE + obj = get_constant(Py_CONSTANT_ONE); + expected = PyLong_FromLong(1); + assert(expected != NULL); + assert(Py_TYPE(obj) == &PyLong_Type); + assert(PyObject_RichCompareBool(obj, expected, Py_EQ) == 1); + CLEAR(obj); + Py_DECREF(expected); + + // Py_CONSTANT_EMPTY_STR + obj = get_constant(Py_CONSTANT_EMPTY_STR); + assert(Py_TYPE(obj) == &PyUnicode_Type); +#if PY_VERSION_HEX >= 0x03030000 + assert(PyUnicode_GetLength(obj) == 0); +#else + assert(PyUnicode_GetSize(obj) == 0); +#endif + CLEAR(obj); + + // Py_CONSTANT_EMPTY_BYTES + obj = get_constant(Py_CONSTANT_EMPTY_BYTES); + assert(Py_TYPE(obj) == &PyBytes_Type); + assert(PyBytes_Size(obj) == 0); + CLEAR(obj); + + // Py_CONSTANT_EMPTY_TUPLE + obj = get_constant(Py_CONSTANT_EMPTY_TUPLE); + assert(Py_TYPE(obj) == &PyTuple_Type); + assert(PyTuple_Size(obj) == 0); + CLEAR(obj); + +#undef CLEAR +} + + +static PyObject * +test_get_constant(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + check_get_constant(Py_GetConstant, 0); + check_get_constant(Py_GetConstantBorrowed, 1); + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -1609,6 +1694,7 @@ static struct PyMethodDef methods[] = { #ifdef TEST_PYTIME {"test_time", test_time, METH_NOARGS, _Py_NULL}, #endif + {"test_get_constant", test_get_constant, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} };