Skip to content

Commit e31c22d

Browse files
authored
gh-111489: Add PyTuple_FromArray() function (#139691)
1 parent 8f14bdd commit e31c22d

File tree

8 files changed

+76
-2
lines changed

8 files changed

+76
-2
lines changed

Doc/c-api/tuple.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ Tuple Objects
3737
or ``NULL`` with an exception set on failure.
3838
3939
40+
.. c:function:: PyObject* PyTuple_FromArray(PyObject *const *array, Py_ssize_t size)
41+
42+
Create a tuple of *size* items and copy references from *array* to the new
43+
tuple.
44+
45+
*array* can be NULL if *size* is ``0``.
46+
47+
On success, return a new reference.
48+
On error, set an exception and return ``NULL``.
49+
50+
.. versionadded:: next
51+
52+
4053
.. c:function:: PyObject* PyTuple_Pack(Py_ssize_t n, ...)
4154
4255
Return a new tuple object of size *n*,

Doc/whatsnew/3.15.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,9 @@ New features
852852

853853
(Contributed by Victor Stinner in :gh:`129813`.)
854854

855+
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
856+
(Contributed by Victor Stinner in :gh:`111489`.)
857+
855858

856859
Porting to Python 3.15
857860
----------------------

Include/cpython/tupleobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@ PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) {
3838
}
3939
#define PyTuple_SET_ITEM(op, index, value) \
4040
PyTuple_SET_ITEM(_PyObject_CAST(op), (index), _PyObject_CAST(value))
41+
42+
PyAPI_FUNC(PyObject*) PyTuple_FromArray(
43+
PyObject *const *array,
44+
Py_ssize_t size);

Include/internal/pycore_tuple.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *);
2323

2424
#define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item)
2525

26-
PyAPI_FUNC(PyObject *)_PyTuple_FromArray(PyObject *const *, Py_ssize_t);
26+
// Alias for backward compatibility
27+
#define _PyTuple_FromArray PyTuple_FromArray
28+
2729
PyAPI_FUNC(PyObject *)_PyTuple_FromStackRefStealOnSuccess(const union _PyStackRef *, Py_ssize_t);
2830
PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t);
2931

Lib/test/test_capi/test_tuple.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,28 @@ def test_tuple_new(self):
6262
self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN)
6363
self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX)
6464

65+
def test_tuple_fromarray(self):
66+
# Test PyTuple_FromArray()
67+
tuple_fromarray = _testcapi.tuple_fromarray
68+
69+
tup = tuple([i] for i in range(5))
70+
copy = tuple_fromarray(tup)
71+
self.assertEqual(copy, tup)
72+
73+
tup = ()
74+
copy = tuple_fromarray(tup)
75+
self.assertIs(copy, tup)
76+
77+
copy = tuple_fromarray(NULL, 0)
78+
self.assertIs(copy, ())
79+
80+
with self.assertRaises(SystemError):
81+
tuple_fromarray(NULL, -1)
82+
with self.assertRaises(SystemError):
83+
tuple_fromarray(NULL, PY_SSIZE_T_MIN)
84+
with self.assertRaises(MemoryError):
85+
tuple_fromarray(NULL, PY_SSIZE_T_MAX)
86+
6587
def test_tuple_pack(self):
6688
# Test PyTuple_Pack()
6789
pack = _testlimitedcapi.tuple_pack
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
2+
Patch by Victor Stinner.

Modules/_testcapi/tuple.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,40 @@ _check_tuple_item_is_NULL(PyObject *Py_UNUSED(module), PyObject *args)
104104
}
105105

106106

107+
static PyObject *
108+
tuple_fromarray(PyObject* Py_UNUSED(module), PyObject *args)
109+
{
110+
PyObject *src;
111+
Py_ssize_t size = UNINITIALIZED_SIZE;
112+
if (!PyArg_ParseTuple(args, "O|n", &src, &size)) {
113+
return NULL;
114+
}
115+
if (src != Py_None && !PyTuple_Check(src)) {
116+
PyErr_SetString(PyExc_TypeError, "expect a tuple");
117+
return NULL;
118+
}
119+
120+
PyObject **items;
121+
if (src != Py_None) {
122+
items = &PyTuple_GET_ITEM(src, 0);
123+
if (size == UNINITIALIZED_SIZE) {
124+
size = PyTuple_GET_SIZE(src);
125+
}
126+
}
127+
else {
128+
items = NULL;
129+
}
130+
return PyTuple_FromArray(items, size);
131+
}
132+
133+
107134
static PyMethodDef test_methods[] = {
108135
{"tuple_get_size", tuple_get_size, METH_O},
109136
{"tuple_get_item", tuple_get_item, METH_VARARGS},
110137
{"tuple_set_item", tuple_set_item, METH_VARARGS},
111138
{"_tuple_resize", _tuple_resize, METH_VARARGS},
112139
{"_check_tuple_item_is_NULL", _check_tuple_item_is_NULL, METH_VARARGS},
140+
{"tuple_fromarray", tuple_fromarray, METH_VARARGS},
113141
{NULL},
114142
};
115143

Objects/tupleobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ tuple_item(PyObject *op, Py_ssize_t i)
366366
}
367367

368368
PyObject *
369-
_PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
369+
PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
370370
{
371371
if (n == 0) {
372372
return tuple_get_empty();

0 commit comments

Comments
 (0)