https://github.com/python/cpython/commit/e31c22dbf9b35bdc6b63871f2ca9a0a0d6786b28
commit: e31c22dbf9b35bdc6b63871f2ca9a0a0d6786b28
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2025-10-10T08:54:12+02:00
summary:
gh-111489: Add PyTuple_FromArray() function (#139691)
files:
A Misc/NEWS.d/next/C_API/2025-10-07-12-51-32.gh-issue-111489.LCKKlg.rst
M Doc/c-api/tuple.rst
M Doc/whatsnew/3.15.rst
M Include/cpython/tupleobject.h
M Include/internal/pycore_tuple.h
M Lib/test/test_capi/test_tuple.py
M Modules/_testcapi/tuple.c
M Objects/tupleobject.c
diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst
index 815afddad19df1..65f8334c437974 100644
--- a/Doc/c-api/tuple.rst
+++ b/Doc/c-api/tuple.rst
@@ -37,6 +37,19 @@ Tuple Objects
or ``NULL`` with an exception set on failure.
+.. c:function:: PyObject* PyTuple_FromArray(PyObject *const *array, Py_ssize_t
size)
+
+ Create a tuple of *size* items and copy references from *array* to the new
+ tuple.
+
+ *array* can be NULL if *size* is ``0``.
+
+ On success, return a new reference.
+ On error, set an exception and return ``NULL``.
+
+ .. versionadded:: next
+
+
.. c:function:: PyObject* PyTuple_Pack(Py_ssize_t n, ...)
Return a new tuple object of size *n*,
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 4b176d6c8e6034..40286d4fe857e8 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -852,6 +852,9 @@ New features
(Contributed by Victor Stinner in :gh:`129813`.)
+* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
+ (Contributed by Victor Stinner in :gh:`111489`.)
+
Porting to Python 3.15
----------------------
diff --git a/Include/cpython/tupleobject.h b/Include/cpython/tupleobject.h
index afb98ccbb81b2d..888baaf3358267 100644
--- a/Include/cpython/tupleobject.h
+++ b/Include/cpython/tupleobject.h
@@ -38,3 +38,7 @@ PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject
*value) {
}
#define PyTuple_SET_ITEM(op, index, value) \
PyTuple_SET_ITEM(_PyObject_CAST(op), (index), _PyObject_CAST(value))
+
+PyAPI_FUNC(PyObject*) PyTuple_FromArray(
+ PyObject *const *array,
+ Py_ssize_t size);
diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h
index acf1bec46028ac..be1961cbf77a2d 100644
--- a/Include/internal/pycore_tuple.h
+++ b/Include/internal/pycore_tuple.h
@@ -23,7 +23,9 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState
*);
#define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item)
-PyAPI_FUNC(PyObject *)_PyTuple_FromArray(PyObject *const *, Py_ssize_t);
+// Alias for backward compatibility
+#define _PyTuple_FromArray PyTuple_FromArray
+
PyAPI_FUNC(PyObject *)_PyTuple_FromStackRefStealOnSuccess(const union
_PyStackRef *, Py_ssize_t);
PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t);
diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py
index 7c07bc64e247c5..b6d6da008d0b7b 100644
--- a/Lib/test/test_capi/test_tuple.py
+++ b/Lib/test/test_capi/test_tuple.py
@@ -62,6 +62,28 @@ def test_tuple_new(self):
self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN)
self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX)
+ def test_tuple_fromarray(self):
+ # Test PyTuple_FromArray()
+ tuple_fromarray = _testcapi.tuple_fromarray
+
+ tup = tuple([i] for i in range(5))
+ copy = tuple_fromarray(tup)
+ self.assertEqual(copy, tup)
+
+ tup = ()
+ copy = tuple_fromarray(tup)
+ self.assertIs(copy, tup)
+
+ copy = tuple_fromarray(NULL, 0)
+ self.assertIs(copy, ())
+
+ with self.assertRaises(SystemError):
+ tuple_fromarray(NULL, -1)
+ with self.assertRaises(SystemError):
+ tuple_fromarray(NULL, PY_SSIZE_T_MIN)
+ with self.assertRaises(MemoryError):
+ tuple_fromarray(NULL, PY_SSIZE_T_MAX)
+
def test_tuple_pack(self):
# Test PyTuple_Pack()
pack = _testlimitedcapi.tuple_pack
diff --git
a/Misc/NEWS.d/next/C_API/2025-10-07-12-51-32.gh-issue-111489.LCKKlg.rst
b/Misc/NEWS.d/next/C_API/2025-10-07-12-51-32.gh-issue-111489.LCKKlg.rst
new file mode 100644
index 00000000000000..9c044f7796bd94
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-10-07-12-51-32.gh-issue-111489.LCKKlg.rst
@@ -0,0 +1,2 @@
+Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
+Patch by Victor Stinner.
diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c
index d9c02ba0ff04fe..5de1c494c0a8c0 100644
--- a/Modules/_testcapi/tuple.c
+++ b/Modules/_testcapi/tuple.c
@@ -104,12 +104,40 @@ _check_tuple_item_is_NULL(PyObject *Py_UNUSED(module),
PyObject *args)
}
+static PyObject *
+tuple_fromarray(PyObject* Py_UNUSED(module), PyObject *args)
+{
+ PyObject *src;
+ Py_ssize_t size = UNINITIALIZED_SIZE;
+ if (!PyArg_ParseTuple(args, "O|n", &src, &size)) {
+ return NULL;
+ }
+ if (src != Py_None && !PyTuple_Check(src)) {
+ PyErr_SetString(PyExc_TypeError, "expect a tuple");
+ return NULL;
+ }
+
+ PyObject **items;
+ if (src != Py_None) {
+ items = &PyTuple_GET_ITEM(src, 0);
+ if (size == UNINITIALIZED_SIZE) {
+ size = PyTuple_GET_SIZE(src);
+ }
+ }
+ else {
+ items = NULL;
+ }
+ return PyTuple_FromArray(items, size);
+}
+
+
static PyMethodDef test_methods[] = {
{"tuple_get_size", tuple_get_size, METH_O},
{"tuple_get_item", tuple_get_item, METH_VARARGS},
{"tuple_set_item", tuple_set_item, METH_VARARGS},
{"_tuple_resize", _tuple_resize, METH_VARARGS},
{"_check_tuple_item_is_NULL", _check_tuple_item_is_NULL, METH_VARARGS},
+ {"tuple_fromarray", tuple_fromarray, METH_VARARGS},
{NULL},
};
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index 9b31758485ca5e..1fa4bae638a1fe 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -366,7 +366,7 @@ tuple_item(PyObject *op, Py_ssize_t i)
}
PyObject *
-_PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
+PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
{
if (n == 0) {
return tuple_get_empty();
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]