diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index 14a7c05efeac87..8e73b053107416 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -58,6 +58,26 @@ Tuple Objects ``PyTuple_Pack(2, a, b)`` is equivalent to ``Py_BuildValue("(OO)", a, b)``. +.. c:function:: PyObject* PyTuple_MakeSingle(PyObject *one) + + Return a new tuple object of size 1, + or ``NULL`` with an exception set on failure. The tuple value + is initialized with a new reference to the *one* object. + ``PyTuple_MakeSingle(a)`` is equivalent to ``PyTuple_Pack(1, a)``. + + *one* must not be ``NULL``. + + +.. c:function:: PyObject* PyTuple_MakePair(PyObject *one, PyObject *two) + + Return a new tuple object of size 2, + or ``NULL`` with an exception set on failure. The tuple values + are initialized with new references to the *one* and *two* objects. + ``PyTuple_MakePair(a, b)`` is equivalent to ``PyTuple_Pack(2, a, b)``. + + *one* and *two* must not be ``NULL``. + + .. c:function:: Py_ssize_t PyTuple_Size(PyObject *p) Take a pointer to a tuple object, and return the size of that tuple. diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 48f4f4919e8966..625e8e63ec9d7e 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -2359,6 +2359,13 @@ PyTuple_GetSlice:PyObject*:p:0: PyTuple_GetSlice:Py_ssize_t:low:: PyTuple_GetSlice:Py_ssize_t:high:: +PyTuple_MakePair:PyObject*::+1: +PyTuple_MakePair:PyObject*:one:+1: +PyTuple_MakePair:PyObject*:two:+1: + +PyTuple_MakeSingle:PyObject*::+1: +PyTuple_MakeSingle:PyObject*:one:+1: + PyTuple_New:PyObject*::+1: PyTuple_New:Py_ssize_t:len:: diff --git a/Include/cpython/tupleobject.h b/Include/cpython/tupleobject.h index 888baaf3358267..3a3653ebba3584 100644 --- a/Include/cpython/tupleobject.h +++ b/Include/cpython/tupleobject.h @@ -42,3 +42,6 @@ PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) { PyAPI_FUNC(PyObject*) PyTuple_FromArray( PyObject *const *array, Py_ssize_t size); + +PyAPI_FUNC(PyObject *) PyTuple_MakeSingle(PyObject *one); +PyAPI_FUNC(PyObject *) PyTuple_MakePair(PyObject *one, PyObject *two); diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index b6d6da008d0b7b..ce70f19c932d3e 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -99,6 +99,35 @@ def test_tuple_pack(self): # CRASHES pack(1, NULL) # CRASHES pack(2, [1]) + def test_tuple_make_single(self): + # Test PyTuple_MakeSingle() + make_single = _testcapi.tuple_make_single + + self.assertEqual(make_single(1), (1,)) + self.assertEqual(make_single(None), (None,)) + self.assertEqual(make_single(True), (True,)) + + temp = object() + self.assertEqual(make_single(temp), (temp,)) + + self.assertRaises(TypeError, make_single, 1, 2) + self.assertRaises(TypeError, make_single) + + def test_tuple_make_pair(self): + # Test PyTuple_MakePair() + make_pair = _testcapi.tuple_make_pair + + self.assertEqual(make_pair(1, 2), (1, 2)) + self.assertEqual(make_pair(None, None), (None, None)) + self.assertEqual(make_pair(True, False), (True, False)) + + temp = object() + self.assertEqual(make_pair(temp, temp), (temp, temp)) + + self.assertRaises(TypeError, make_pair, 1, 2, 3) + self.assertRaises(TypeError, make_pair, 1) + self.assertRaises(TypeError, make_pair) + def test_tuple_size(self): # Test PyTuple_Size() size = _testlimitedcapi.tuple_size diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-01-30-28.gh-issue-140052.08spgX.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-01-30-28.gh-issue-140052.08spgX.rst new file mode 100644 index 00000000000000..c0c93598d0df49 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-01-30-28.gh-issue-140052.08spgX.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyTuple_MakeSingle` and :c:func:`PyTuple_MakePair` functions to +create short-sized tuples. Patch by Sergey Miryanov. diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 5de1c494c0a8c0..32574f13d0d937 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -130,6 +130,23 @@ tuple_fromarray(PyObject* Py_UNUSED(module), PyObject *args) return PyTuple_FromArray(items, size); } +static PyObject * +tuple_make_single(PyObject *Py_UNUSED(module), PyObject *one) +{ + return PyTuple_MakeSingle(one); +} + +static PyObject * +tuple_make_pair(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *one, *two; + if (!PyArg_ParseTuple(args, "OO", &one, &two)) { + return NULL; + } + + return PyTuple_MakePair(one, two); +} + static PyMethodDef test_methods[] = { {"tuple_get_size", tuple_get_size, METH_O}, @@ -138,6 +155,8 @@ static PyMethodDef test_methods[] = { {"_tuple_resize", _tuple_resize, METH_VARARGS}, {"_check_tuple_item_is_NULL", _check_tuple_item_is_NULL, METH_VARARGS}, {"tuple_fromarray", tuple_fromarray, METH_VARARGS}, + {"tuple_make_single", tuple_make_single, METH_O}, + {"tuple_make_pair", tuple_make_pair, METH_VARARGS}, {NULL}, }; diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 94b7ae7e642283..19443a52a79f20 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -184,6 +184,35 @@ PyTuple_Pack(Py_ssize_t n, ...) return (PyObject *)result; } +PyObject * +PyTuple_MakeSingle(PyObject *one) +{ + assert (one != NULL); + + PyTupleObject *op = tuple_alloc(1); + if (op == NULL) { + return NULL; + } + op->ob_item[0] = Py_NewRef(one); + _PyObject_GC_TRACK(op); + return (PyObject *)op; +} + +PyObject * +PyTuple_MakePair(PyObject *one, PyObject *two) +{ + assert (one != NULL); + assert (two != NULL); + + PyTupleObject *op = tuple_alloc(2); + if (op == NULL) { + return NULL; + } + op->ob_item[0] = Py_NewRef(one); + op->ob_item[1] = Py_NewRef(two); + _PyObject_GC_TRACK(op); + return (PyObject *)op; +} /* Methods */