]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-111489: Add PyTuple_FromArray() function (#139691)
authorVictor Stinner <vstinner@python.org>
Fri, 10 Oct 2025 06:54:12 +0000 (08:54 +0200)
committerGitHub <noreply@github.com>
Fri, 10 Oct 2025 06:54:12 +0000 (08:54 +0200)
Doc/c-api/tuple.rst
Doc/whatsnew/3.15.rst
Include/cpython/tupleobject.h
Include/internal/pycore_tuple.h
Lib/test/test_capi/test_tuple.py
Misc/NEWS.d/next/C_API/2025-10-07-12-51-32.gh-issue-111489.LCKKlg.rst [new file with mode: 0644]
Modules/_testcapi/tuple.c
Objects/tupleobject.c

index 815afddad19df1a292f25b16ef221143ce57c724..65f8334c437974bcc9714ee2722aee1808e5032d 100644 (file)
@@ -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*,
index 4b176d6c8e60340994d0c87f5d252294622956a2..40286d4fe857e878b5bdfa57d62c132b25f359d2 100644 (file)
@@ -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
 ----------------------
index afb98ccbb81b2de63febb25bb19dce489191f2e2..888baaf3358267ec00864a4e8dbea7f6b5d45e2b 100644 (file)
@@ -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);
index acf1bec46028acd753fd53763b9a9ff9ccb30485..be1961cbf77a2d7548daec2a25676b6437540c21 100644 (file)
@@ -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);
 
index 7c07bc64e247c50fa86c3aa3462f6d9d2c3ca523..b6d6da008d0b7b059f740cd5a4ee6a4217512fb6 100644 (file)
@@ -62,6 +62,28 @@ class CAPITest(unittest.TestCase):
         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 (file)
index 0000000..9c044f7
--- /dev/null
@@ -0,0 +1,2 @@
+Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
+Patch by Victor Stinner.
index d9c02ba0ff04feee8a837848da70791872017410..5de1c494c0a8c00cd3304258badd77ea8587d74e 100644 (file)
@@ -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},
 };
 
index 9b31758485ca5e525cae94c0c7b4425fd9510ac7..1fa4bae638a1feaae07af80b4f01b21d3c88ab31 100644 (file)
@@ -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();