gh-111489: Add PyTuple_FromArray() function (#139691)

This commit is contained in:
Victor Stinner 2025-10-10 08:54:12 +02:00 committed by GitHub
parent 8f14bddeae
commit e31c22dbf9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 76 additions and 2 deletions

View file

@ -37,6 +37,19 @@ Tuple Objects
or ``NULL`` with an exception set on failure. 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, ...) .. c:function:: PyObject* PyTuple_Pack(Py_ssize_t n, ...)
Return a new tuple object of size *n*, Return a new tuple object of size *n*,

View file

@ -852,6 +852,9 @@ New features
(Contributed by Victor Stinner in :gh:`129813`.) (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 Porting to Python 3.15
---------------------- ----------------------

View file

@ -38,3 +38,7 @@ PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) {
} }
#define PyTuple_SET_ITEM(op, index, value) \ #define PyTuple_SET_ITEM(op, index, value) \
PyTuple_SET_ITEM(_PyObject_CAST(op), (index), _PyObject_CAST(value)) PyTuple_SET_ITEM(_PyObject_CAST(op), (index), _PyObject_CAST(value))
PyAPI_FUNC(PyObject*) PyTuple_FromArray(
PyObject *const *array,
Py_ssize_t size);

View file

@ -23,7 +23,9 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *);
#define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item) #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_FromStackRefStealOnSuccess(const union _PyStackRef *, Py_ssize_t);
PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t);

View file

@ -62,6 +62,28 @@ def test_tuple_new(self):
self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN) self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN)
self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX) 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): def test_tuple_pack(self):
# Test PyTuple_Pack() # Test PyTuple_Pack()
pack = _testlimitedcapi.tuple_pack pack = _testlimitedcapi.tuple_pack

View file

@ -0,0 +1,2 @@
Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
Patch by Victor Stinner.

View 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[] = { static PyMethodDef test_methods[] = {
{"tuple_get_size", tuple_get_size, METH_O}, {"tuple_get_size", tuple_get_size, METH_O},
{"tuple_get_item", tuple_get_item, METH_VARARGS}, {"tuple_get_item", tuple_get_item, METH_VARARGS},
{"tuple_set_item", tuple_set_item, METH_VARARGS}, {"tuple_set_item", tuple_set_item, METH_VARARGS},
{"_tuple_resize", _tuple_resize, METH_VARARGS}, {"_tuple_resize", _tuple_resize, METH_VARARGS},
{"_check_tuple_item_is_NULL", _check_tuple_item_is_NULL, METH_VARARGS}, {"_check_tuple_item_is_NULL", _check_tuple_item_is_NULL, METH_VARARGS},
{"tuple_fromarray", tuple_fromarray, METH_VARARGS},
{NULL}, {NULL},
}; };

View file

@ -366,7 +366,7 @@ tuple_item(PyObject *op, Py_ssize_t i)
} }
PyObject * PyObject *
_PyTuple_FromArray(PyObject *const *src, Py_ssize_t n) PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
{ {
if (n == 0) { if (n == 0) {
return tuple_get_empty(); return tuple_get_empty();