Skip to content

Commit

Permalink
Add Py_GetConstant() and Py_GetConstantBorrowed()
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Mar 21, 2024
1 parent 7000d0e commit ef30d63
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 0 deletions.
9 changes: 9 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@ Python 3.12
Not available on PyPy.
.. c:function:: PyObject* Py_GetConstant(unsigned int constant_id)
See `Py_GetConstant() documentation <https://docs.python.org/dev/c-api/object.html#c.Py_GetConstant>`__.
.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id)
See `Py_GetConstantBorrowed() documentation <https://docs.python.org/dev/c-api/object.html#c.Py_GetConstantBorrowed>`__.
Not supported:
* ``PyDict_AddWatcher()``, ``PyDict_Watch()``.
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

* 2024-03-21: Add functions:

* ``Py_GetConstant()``
* ``Py_GetConstantBorrowed()``

* 2024-03-09: Add hash constants:

* ``PyHASH_BITS``
Expand Down
77 changes: 77 additions & 0 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions tests/test_pythoncapi_compat_cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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}
};

Expand Down

0 comments on commit ef30d63

Please sign in to comment.