Skip to content

Commit ce1b2ff

Browse files
committed
gh-111489: Add PyTuple_FromArray() function
1 parent 134ff81 commit ce1b2ff

File tree

8 files changed

+74
-2
lines changed

8 files changed

+74
-2
lines changed

Doc/c-api/tuple.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ 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+
* Return a new reference on success.
46+
* Set an exception and return ``NULL`` on error.
47+
48+
.. versionadded:: next
49+
50+
4051
.. c:function:: PyObject* PyTuple_Pack(Py_ssize_t n, ...)
4152
4253
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,14 @@ def my_iter():
279279
self.assertEqual(tuple(my_iter()), (TAG, *range(10)))
280280
self.assertEqual(tuples, [])
281281

282+
def test_tuple_fromarray(self):
283+
# Test PyTuple_FromArray()
284+
tuple_fromarray = _testcapi.tuple_fromarray
285+
286+
tup = tuple(object() for _ in range(5))
287+
copy = tuple_fromarray(tup)
288+
self.assertEqual(copy, tup)
289+
282290

283291
if __name__ == "__main__":
284292
unittest.main()
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: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,54 @@ _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+
if (!PyArg_ParseTuple(args, "O!", &PyTuple_Type, &src)) {
112+
return NULL;
113+
}
114+
115+
Py_ssize_t size = PyTuple_GET_SIZE(src);
116+
PyObject **array = PyMem_Malloc(size * sizeof(PyObject**));
117+
if (array == NULL) {
118+
PyErr_NoMemory();
119+
return NULL;
120+
}
121+
for (Py_ssize_t i = 0; i < size; i++) {
122+
array[i] = Py_NewRef(PyTuple_GET_ITEM(src, i));
123+
}
124+
125+
PyObject *tuple = PyTuple_FromArray(array, size);
126+
if (tuple == NULL) {
127+
goto done;
128+
}
129+
130+
for (Py_ssize_t i = 0; i < size; i++) {
131+
// check that the array is not modified
132+
assert(array[i] == PyTuple_GET_ITEM(src, i));
133+
134+
// check that the array was copied properly
135+
assert(PyTuple_GET_ITEM(tuple, i) == array[i]);
136+
}
137+
138+
done:
139+
for (Py_ssize_t i = 0; i < size; i++) {
140+
Py_DECREF(array[i]);
141+
}
142+
PyMem_Free(array);
143+
144+
return tuple;
145+
}
146+
147+
107148
static PyMethodDef test_methods[] = {
108149
{"tuple_get_size", tuple_get_size, METH_O},
109150
{"tuple_get_item", tuple_get_item, METH_VARARGS},
110151
{"tuple_set_item", tuple_set_item, METH_VARARGS},
111152
{"_tuple_resize", _tuple_resize, METH_VARARGS},
112153
{"_check_tuple_item_is_NULL", _check_tuple_item_is_NULL, METH_VARARGS},
154+
{"tuple_fromarray", tuple_fromarray, METH_VARARGS},
113155
{NULL},
114156
};
115157

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)