diff --git a/Doc/c-api/list.rst b/Doc/c-api/list.rst index c8b64bad702f50..b21836ca2864e8 100644 --- a/Doc/c-api/list.rst +++ b/Doc/c-api/list.rst @@ -43,6 +43,19 @@ List Objects setting all items to a real object with :c:func:`PyList_SetItem`. +.. c:function:: PyObject* PyList_FromArrayMoveRef(PyObject *const *array, Py_ssize_t size) + + Create a list of *size* items and move *array* :term:`strong references + ` to the new list. + + * Return a new reference on success. *array* references become + :term:`borrowed references `. + * Set an exception and return ``NULL`` on error. *array* is left unchanged, + the caller should clear *array* strong references. + + .. versionadded:: 3.13 + + .. c:function:: Py_ssize_t PyList_Size(PyObject *list) .. index:: pair: built-in function; len diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index b3710560ebe7ac..01846c21f6d2ec 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -36,6 +36,30 @@ Tuple Objects Return a new tuple object of size *len*, or ``NULL`` 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. + + * Return a new reference on success. + * Set an exception and return NULL on error. + + .. versionadded:: 3.13 + + +.. c:function:: PyObject* PyTuple_FromArrayMoveRef(PyObject *const *array, Py_ssize_t size) + + Create a tuple of *size* items and move *array* :term:`strong references + ` to the new tuple. + + * Return a new reference on success. *array* references become :term:`borrowed + references `. + * Set an exception and return ``NULL`` on error. *array* is left unchanged, + the caller should clear *array* strong references. + + .. versionadded:: 3.13 + + .. c:function:: PyObject* PyTuple_Pack(Py_ssize_t n, ...) Return a new tuple object of size *n*, or ``NULL`` on failure. The tuple values diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 00f396846e29bd..cc8129a066ba6a 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1269,6 +1269,14 @@ New Features * Add :c:func:`Py_HashPointer` function to hash a pointer. (Contributed by Victor Stinner in :gh:`111545`.) +* Add new functions to create :class:`tuple` and :class:`list` from arrays: + + * :c:func:`PyTuple_FromArray`. + * :c:func:`PyTuple_FromArrayMoveRef`. + * :c:func:`PyList_FromArrayMoveRef`. + + (Contributed by Victor Stinner in :gh:`111489`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/cpython/listobject.h b/Include/cpython/listobject.h index 8ade1b164681f9..fce1f7b9d27c7e 100644 --- a/Include/cpython/listobject.h +++ b/Include/cpython/listobject.h @@ -47,3 +47,6 @@ PyList_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) { PyAPI_FUNC(int) PyList_Extend(PyObject *self, PyObject *iterable); PyAPI_FUNC(int) PyList_Clear(PyObject *self); +PyAPI_FUNC(PyObject*) PyList_FromArrayMoveRef( + PyObject *const *array, + Py_ssize_t size); diff --git a/Include/cpython/tupleobject.h b/Include/cpython/tupleobject.h index e530c8beda44ab..0e7a08f2af225a 100644 --- a/Include/cpython/tupleobject.h +++ b/Include/cpython/tupleobject.h @@ -36,3 +36,10 @@ 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); +PyAPI_FUNC(PyObject*) PyTuple_FromArrayMoveRef( + PyObject *const *array, + Py_ssize_t size); diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 55d67b32bc8a63..e47b0f954eaa7a 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -77,7 +77,9 @@ typedef struct { PyListObject *it_seq; /* Set to NULL when iterator is exhausted */ } _PyListIterObject; -extern PyObject *_PyList_FromArraySteal(PyObject *const *src, Py_ssize_t n); +extern PyObject *_PyList_FromArraySteal( + PyObject *const *array, + Py_ssize_t size); #ifdef __cplusplus } diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index 4fa7a12206bcb2..3584724aad56bc 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -63,8 +63,11 @@ struct _Py_tuple_state { #define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item) -extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t); -extern PyObject *_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); +// Alias for backward compatibility +#define _PyTuple_FromArray PyTuple_FromArray +extern PyObject *_PyTuple_FromArraySteal( + PyObject *const *array, + Py_ssize_t size); typedef struct { diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py index eb03d51d3def37..263c4e8247374f 100644 --- a/Lib/test/test_capi/test_list.py +++ b/Lib/test/test_capi/test_list.py @@ -342,3 +342,11 @@ def test_list_extend(self): # CRASHES list_extend(NULL, []) # CRASHES list_extend([], NULL) + + def test_list_fromarraymoveref(self): + # Test PyList_FromArrayMoveRef() + list_fromarraymoveref = _testcapi.list_fromarraymoveref + + lst = [object() for _ in range(5)] + copy = list_fromarraymoveref(lst) + self.assertEqual(copy, lst) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py new file mode 100644 index 00000000000000..7819c9339d8d25 --- /dev/null +++ b/Lib/test/test_capi/test_tuple.py @@ -0,0 +1,21 @@ +import unittest +from test.support import import_helper +_testcapi = import_helper.import_module('_testcapi') + + +class CAPITest(unittest.TestCase): + def test_tuple_fromarray(self): + # Test PyTuple_FromArray() + tuple_fromarray = _testcapi.tuple_fromarraymoveref + + tup = tuple(object() for _ in range(5)) + copy = tuple_fromarray(tup) + self.assertEqual(copy, tup) + + def test_tuple_fromarraymoveref(self): + # Test PyTuple_FromArrayMoveRef() + tuple_fromarraymoveref = _testcapi.tuple_fromarraymoveref + + tup = tuple(object() for _ in range(5)) + copy = tuple_fromarraymoveref(tup) + self.assertEqual(copy, tup) diff --git a/Misc/NEWS.d/next/C API/2023-12-11-18-30-51.gh-issue-111489.S78zth.rst b/Misc/NEWS.d/next/C API/2023-12-11-18-30-51.gh-issue-111489.S78zth.rst new file mode 100644 index 00000000000000..d8207585046716 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-12-11-18-30-51.gh-issue-111489.S78zth.rst @@ -0,0 +1,7 @@ +Add new functions to create :class:`tuple` and :class:`list` from arrays: + +* :c:func:`PyTuple_FromArray`. +* :c:func:`PyTuple_FromArrayMoveRef`. +* :c:func:`PyList_FromArrayMoveRef`. + +Patch by Victor Stinner. diff --git a/Modules/_testcapi/list.c b/Modules/_testcapi/list.c index 10e18699f01bc1..cce6e8408a9a16 100644 --- a/Modules/_testcapi/list.c +++ b/Modules/_testcapi/list.c @@ -183,6 +183,45 @@ list_extend(PyObject* Py_UNUSED(module), PyObject *args) } +static PyObject * +list_fromarraymoveref(PyObject* Py_UNUSED(module), PyObject *args) +{ + PyObject *src; + if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &src)) { + return NULL; + } + + Py_ssize_t size = PyList_GET_SIZE(src); + PyObject **array = PyMem_Malloc(size * sizeof(PyObject**)); + if (array == NULL) { + PyErr_NoMemory(); + return NULL; + } + for (Py_ssize_t i = 0; i < size; i++) { + array[i] = Py_NewRef(PyList_GET_ITEM(src, i)); + } + + PyObject *list = PyList_FromArrayMoveRef(array, size); + + for (Py_ssize_t i = 0; i < size; i++) { + // check that the array is not modified + assert(array[i] == PyList_GET_ITEM(src, i)); + + // check that the array was copied properly + assert(PyList_GET_ITEM(list, i) == array[i]); + } + + if (list == NULL) { + for (Py_ssize_t i = 0; i < size; i++) { + Py_DECREF(array[i]); + } + } + PyMem_Free(array); + + return list; +} + + static PyMethodDef test_methods[] = { {"list_check", list_check, METH_O}, {"list_check_exact", list_check_exact, METH_O}, @@ -202,6 +241,7 @@ static PyMethodDef test_methods[] = { {"list_astuple", list_astuple, METH_O}, {"list_clear", list_clear, METH_O}, {"list_extend", list_extend, METH_VARARGS}, + {"list_fromarraymoveref", list_fromarraymoveref, METH_VARARGS}, {NULL}, }; diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 95dde8c0edadbe..27f338bb789230 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -2,7 +2,68 @@ #include "util.h" +static PyObject * +tuple_fromarray_impl(PyObject *args, int move_ref) +{ + PyObject *src; + if (!PyArg_ParseTuple(args, "O!", &PyTuple_Type, &src)) { + return NULL; + } + + Py_ssize_t size = PyTuple_GET_SIZE(src); + PyObject **array = PyMem_Malloc(size * sizeof(PyObject**)); + if (array == NULL) { + PyErr_NoMemory(); + return NULL; + } + for (Py_ssize_t i = 0; i < size; i++) { + array[i] = Py_NewRef(PyTuple_GET_ITEM(src, i)); + } + + PyObject *tuple; + if (move_ref) { + tuple = PyTuple_FromArrayMoveRef(array, size); + } + else { + tuple = PyTuple_FromArray(array, size); + } + + for (Py_ssize_t i = 0; i < size; i++) { + // check that the array is not modified + assert(array[i] == PyTuple_GET_ITEM(src, i)); + + // check that the array was copied properly + assert(PyTuple_GET_ITEM(tuple, i) == array[i]); + } + + if (!move_ref || tuple == NULL) { + for (Py_ssize_t i = 0; i < size; i++) { + Py_DECREF(array[i]); + } + } + PyMem_Free(array); + + return tuple; +} + + +static PyObject * +tuple_fromarray(PyObject* Py_UNUSED(module), PyObject *args) +{ + return tuple_fromarray_impl(args, 0); +} + + +static PyObject * +tuple_fromarraymoveref(PyObject* Py_UNUSED(module), PyObject *args) +{ + return tuple_fromarray_impl(args, 1); +} + + static PyMethodDef test_methods[] = { + {"tuple_fromarray", tuple_fromarray, METH_VARARGS}, + {"tuple_fromarraymoveref", tuple_fromarraymoveref, METH_VARARGS}, {NULL}, }; diff --git a/Objects/listobject.c b/Objects/listobject.c index 2d04218439bd20..05bede53a1e775 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -2622,25 +2622,36 @@ PyList_AsTuple(PyObject *v) return _PyTuple_FromArray(((PyListObject *)v)->ob_item, Py_SIZE(v)); } -PyObject * -_PyList_FromArraySteal(PyObject *const *src, Py_ssize_t n) +PyObject* +PyList_FromArrayMoveRef(PyObject *const *array, Py_ssize_t size) { - if (n == 0) { + if (size == 0) { return PyList_New(0); } - PyListObject *list = (PyListObject *)PyList_New(n); - if (list == NULL) { - for (Py_ssize_t i = 0; i < n; i++) { - Py_DECREF(src[i]); - } + PyObject *op = PyList_New(size); + if (op == NULL) { return NULL; } + PyListObject *list = (PyListObject *)op; - PyObject **dst = list->ob_item; - memcpy(dst, src, n * sizeof(PyObject *)); + PyObject **items = list->ob_item; + // PyList_New(size) returns NULL is size*sizeof(PyObject*) is greater than + // PY_SSIZE_T_MAX. No need to check for integer overflow again. + memcpy(items, array, size * sizeof(PyObject *)); + return op; +} - return (PyObject *)list; +PyObject * +_PyList_FromArraySteal(PyObject *const *array, Py_ssize_t size) +{ + PyObject *list = PyList_FromArrayMoveRef(array, size); + if (list == NULL) { + for (Py_ssize_t i = 0; i < size; i++) { + Py_DECREF(array[i]); + } + } + return list; } /*[clinic input] diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index d567839c5e3a0b..4fc16527492dad 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -371,47 +371,60 @@ tupleitem(PyTupleObject *a, Py_ssize_t i) } PyObject * -_PyTuple_FromArray(PyObject *const *src, Py_ssize_t n) +PyTuple_FromArray(PyObject *const *array, Py_ssize_t size) { - if (n == 0) { + if (size == 0) { return tuple_get_empty(); } - PyTupleObject *tuple = tuple_alloc(n); + PyTupleObject *tuple = tuple_alloc(size); if (tuple == NULL) { return NULL; } - PyObject **dst = tuple->ob_item; - for (Py_ssize_t i = 0; i < n; i++) { - PyObject *item = src[i]; - dst[i] = Py_NewRef(item); + + PyObject **items = tuple->ob_item; + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *item = array[i]; + items[i] = Py_NewRef(item); } _PyObject_GC_TRACK(tuple); return (PyObject *)tuple; } PyObject * -_PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n) +PyTuple_FromArrayMoveRef(PyObject *const *array, Py_ssize_t size) { - if (n == 0) { + if (size == 0) { return tuple_get_empty(); } - PyTupleObject *tuple = tuple_alloc(n); + PyTupleObject *tuple = tuple_alloc(size); if (tuple == NULL) { - for (Py_ssize_t i = 0; i < n; i++) { - Py_DECREF(src[i]); + for (Py_ssize_t i = 0; i < size; i++) { + Py_DECREF(array[i]); } return NULL; } - PyObject **dst = tuple->ob_item; - for (Py_ssize_t i = 0; i < n; i++) { - PyObject *item = src[i]; - dst[i] = item; + PyObject **items = tuple->ob_item; + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *item = array[i]; + items[i] = item; } _PyObject_GC_TRACK(tuple); return (PyObject *)tuple; } +PyObject * +_PyTuple_FromArraySteal(PyObject *const *array, Py_ssize_t size) +{ + PyObject *tuple = PyTuple_FromArrayMoveRef(array, size); + if (tuple == NULL) { + for (Py_ssize_t i = 0; i < size; i++) { + Py_DECREF(array[i]); + } + } + return tuple; +} + static PyObject * tupleslice(PyTupleObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) @@ -425,7 +438,7 @@ tupleslice(PyTupleObject *a, Py_ssize_t ilow, if (ilow == 0 && ihigh == Py_SIZE(a) && PyTuple_CheckExact(a)) { return Py_NewRef(a); } - return _PyTuple_FromArray(a->ob_item + ilow, ihigh - ilow); + return PyTuple_FromArray(a->ob_item + ilow, ihigh - ilow); } PyObject *