diff --git a/Doc/c-api/list.rst b/Doc/c-api/list.rst index c15cecd41b89d17..a2e605f4677dfa5 100644 --- a/Doc/c-api/list.rst +++ b/Doc/c-api/list.rst @@ -51,6 +51,17 @@ List Objects ``len(list)`` on a list object. +.. c:function:: int PyList_Clear(PyObject *list) + + Remove all items from *list*. Similar to: + ``PyList_SetSlice(L, 0, PY_SSIZE_T_MAX, NULL)``. + + Raise an exception and return ``-1`` if *list* is not a :class:`list` + object. Return 0 on success. + + .. versionadded:: 3.13 + + .. c:function:: Py_ssize_t PyList_GET_SIZE(PyObject *list) Similar to :c:func:`PyList_Size`, but without error checking. @@ -112,6 +123,17 @@ List Objects to ``list.append(item)``. +.. c:function:: int PyList_Extend(PyObject *self, PyObject *iterable) + + Extend *list* with the contents of *iterable*. Similar to: + ``PyList_SetSlice(L, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable)``. + + Raise an exception and return ``-1`` if *list* is not a :class:`list` + object. Return 0 on success. + + .. versionadded:: 3.13 + + .. c:function:: PyObject* PyList_GetSlice(PyObject *list, Py_ssize_t low, Py_ssize_t high) Return a list of the objects in *list* containing the objects *between* *low* diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ae3c88d28d73a08..9f9239a7eeb0368 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1164,6 +1164,10 @@ New Features :c:func:`PyErr_WriteUnraisable`, but allow to customize the warning mesage. (Contributed by Serhiy Storchaka in :gh:`108082`.) +* Add :c:func:`PyList_Extend` and :c:func:`PyList_Clear` functions: similar to + Python ``list.extend()`` and ``list.clear()`` methods. + (Contributed by Victor Stinner in :gh:`111138`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/listobject.h b/Include/listobject.h index 6b7041ba0b05d59..0883c554db1e67c 100644 --- a/Include/listobject.h +++ b/Include/listobject.h @@ -35,6 +35,8 @@ PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *); PyAPI_FUNC(PyObject *) PyList_GetSlice(PyObject *, Py_ssize_t, Py_ssize_t); PyAPI_FUNC(int) PyList_SetSlice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); +PyAPI_FUNC(int) PyList_Extend(PyObject *self, PyObject *iterable); +PyAPI_FUNC(int) PyList_Clear(PyObject *self); PyAPI_FUNC(int) PyList_Sort(PyObject *); PyAPI_FUNC(int) PyList_Reverse(PyObject *); diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py index 197da03e07fa274..a6c185debb271a6 100644 --- a/Lib/test/test_capi/test_list.py +++ b/Lib/test/test_capi/test_list.py @@ -275,3 +275,39 @@ def test_list_astuple(self): self.assertRaises(SystemError, astuple, ()) self.assertRaises(SystemError, astuple, object()) self.assertRaises(SystemError, astuple, NULL) + + def test_list_clear(self): + # Test PyList_Clear() + list_clear = _testcapi.list_clear + + lst = [1, 2, 3] + self.assertEqual(list_clear(lst), 0) + self.assertEqual(lst, []) + + lst = [] + self.assertEqual(list_clear(lst), 0) + self.assertEqual(lst, []) + + self.assertRaises(TypeError, list_clear, ()) + self.assertRaises(TypeError, list_clear, object()) + + # CRASHES list_clear(NULL) + + def test_list_extend(self): + # Test PyList_Extend() + list_extend = _testcapi.list_extend + + lst = [1] + arg = [2, 3] + self.assertEqual(list_extend(lst, arg), 0) + self.assertEqual(lst, [1, 2, 3]) + + lst = [] + arg = (2, 3) + self.assertEqual(list_extend(lst, arg), 0) + self.assertEqual(lst, [2, 3]) + + self.assertRaises(TypeError, list_extend, [], object()) + + # CRASHES list_extend(NULL, []) + # CRASHES list_extend([], NULL) diff --git a/Misc/NEWS.d/next/C API/2023-11-08-18-37-19.gh-issue-111138.3Ypq8h.rst b/Misc/NEWS.d/next/C API/2023-11-08-18-37-19.gh-issue-111138.3Ypq8h.rst new file mode 100644 index 000000000000000..15c3b9b3a6b9ad0 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-08-18-37-19.gh-issue-111138.3Ypq8h.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyList_Extend` and :c:func:`PyList_Clear` functions: similar to +Python ``list.extend()`` and ``list.clear()`` methods. Patch by Victor Stinner. diff --git a/Modules/_testcapi/list.c b/Modules/_testcapi/list.c index 6ba0e7ab27c5d79..10e18699f01bc1f 100644 --- a/Modules/_testcapi/list.c +++ b/Modules/_testcapi/list.c @@ -162,6 +162,25 @@ list_astuple(PyObject* Py_UNUSED(module), PyObject *obj) } +static PyObject * +list_clear(PyObject* Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + RETURN_INT(PyList_Clear(obj)); +} + + +static PyObject * +list_extend(PyObject* Py_UNUSED(module), PyObject *args) +{ + PyObject *obj, *arg; + if (!PyArg_ParseTuple(args, "OO", &obj, &arg)) { + return NULL; + } + NULLABLE(obj); + NULLABLE(arg); + RETURN_INT(PyList_Extend(obj, arg)); +} static PyMethodDef test_methods[] = { @@ -181,6 +200,8 @@ static PyMethodDef test_methods[] = { {"list_sort", list_sort, METH_O}, {"list_reverse", list_reverse, METH_O}, {"list_astuple", list_astuple, METH_O}, + {"list_clear", list_clear, METH_O}, + {"list_extend", list_extend, METH_VARARGS}, {NULL}, }; diff --git a/Objects/clinic/listobject.c.h b/Objects/clinic/listobject.c.h index b2178cd7f5f3838..54e6060451f3ffb 100644 --- a/Objects/clinic/listobject.c.h +++ b/Objects/clinic/listobject.c.h @@ -50,22 +50,22 @@ list_insert(PyListObject *self, PyObject *const *args, Py_ssize_t nargs) return return_value; } -PyDoc_STRVAR(list_clear__doc__, +PyDoc_STRVAR(py_list_clear__doc__, "clear($self, /)\n" "--\n" "\n" "Remove all items from list."); -#define LIST_CLEAR_METHODDEF \ - {"clear", (PyCFunction)list_clear, METH_NOARGS, list_clear__doc__}, +#define PY_LIST_CLEAR_METHODDEF \ + {"clear", (PyCFunction)py_list_clear, METH_NOARGS, py_list_clear__doc__}, static PyObject * -list_clear_impl(PyListObject *self); +py_list_clear_impl(PyListObject *self); static PyObject * -list_clear(PyListObject *self, PyObject *Py_UNUSED(ignored)) +py_list_clear(PyListObject *self, PyObject *Py_UNUSED(ignored)) { - return list_clear_impl(self); + return py_list_clear_impl(self); } PyDoc_STRVAR(list_copy__doc__, @@ -95,14 +95,14 @@ PyDoc_STRVAR(list_append__doc__, #define LIST_APPEND_METHODDEF \ {"append", (PyCFunction)list_append, METH_O, list_append__doc__}, -PyDoc_STRVAR(list_extend__doc__, +PyDoc_STRVAR(py_list_extend__doc__, "extend($self, iterable, /)\n" "--\n" "\n" "Extend list by appending elements from the iterable."); -#define LIST_EXTEND_METHODDEF \ - {"extend", (PyCFunction)list_extend, METH_O, list_extend__doc__}, +#define PY_LIST_EXTEND_METHODDEF \ + {"extend", (PyCFunction)py_list_extend, METH_O, py_list_extend__doc__}, PyDoc_STRVAR(list_pop__doc__, "pop($self, index=-1, /)\n" @@ -384,4 +384,4 @@ list___reversed__(PyListObject *self, PyObject *Py_UNUSED(ignored)) { return list___reversed___impl(self); } -/*[clinic end generated code: output=5dea9dd3bb219a7f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f2d7b63119464ff4 input=a9049054013a1b77]*/ diff --git a/Objects/listobject.c b/Objects/listobject.c index af006ef0f618504..fe81e4b9ff2abb2 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -592,26 +592,33 @@ list_repeat(PyListObject *a, Py_ssize_t n) return (PyObject *) np; } -static int -_list_clear(PyListObject *a) +static void +list_clear(PyListObject *a) { - Py_ssize_t i; - PyObject **item = a->ob_item; - if (item != NULL) { - /* Because XDECREF can recursively invoke operations on - this list, we make it empty first. */ - i = Py_SIZE(a); - Py_SET_SIZE(a, 0); - a->ob_item = NULL; - a->allocated = 0; - while (--i >= 0) { - Py_XDECREF(item[i]); - } - PyMem_Free(item); + PyObject **items = a->ob_item; + if (items == NULL) { + return; + } + + /* Because XDECREF can recursively invoke operations on + this list, we make it empty first. */ + Py_ssize_t i = Py_SIZE(a); + Py_SET_SIZE(a, 0); + a->ob_item = NULL; + a->allocated = 0; + while (--i >= 0) { + Py_XDECREF(items[i]); } - /* Never fails; the return value can be ignored. - Note that there is no guarantee that the list is actually empty - at this point, because XDECREF may have populated it again! */ + PyMem_Free(items); + + // Note that there is no guarantee that the list is actually empty + // at this point, because XDECREF may have populated it indirectly again! +} + +static int +list_clear_slot(PyListObject *self) +{ + list_clear(self); return 0; } @@ -675,7 +682,8 @@ list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) d = n - norig; if (Py_SIZE(a) + d == 0) { Py_XDECREF(v_as_SF); - return _list_clear(a); + list_clear(a); + return 0; } item = a->ob_item; /* recycle the items that we are about to remove */ @@ -745,7 +753,7 @@ list_inplace_repeat(PyListObject *self, Py_ssize_t n) } if (n < 1) { - (void)_list_clear(self); + list_clear(self); return Py_NewRef(self); } @@ -801,16 +809,16 @@ list_insert_impl(PyListObject *self, Py_ssize_t index, PyObject *object) } /*[clinic input] -list.clear +list.clear as py_list_clear Remove all items from list. [clinic start generated code]*/ static PyObject * -list_clear_impl(PyListObject *self) -/*[clinic end generated code: output=67a1896c01f74362 input=ca3c1646856742f6]*/ +py_list_clear_impl(PyListObject *self) +/*[clinic end generated code: output=83726743807e3518 input=378711e10f545c53]*/ { - _list_clear(self); + list_clear(self); Py_RETURN_NONE; } @@ -846,83 +854,70 @@ list_append(PyListObject *self, PyObject *object) Py_RETURN_NONE; } -/*[clinic input] -list.extend - - iterable: object - / - -Extend list by appending elements from the iterable. -[clinic start generated code]*/ - -static PyObject * -list_extend(PyListObject *self, PyObject *iterable) -/*[clinic end generated code: output=630fb3bca0c8e789 input=9ec5ba3a81be3a4d]*/ +static int +list_extend_fast(PyListObject *self, PyObject *iterable) { - PyObject *it; /* iter(v) */ - Py_ssize_t m; /* size of self */ - Py_ssize_t n; /* guess for size of iterable */ - Py_ssize_t i; - PyObject *(*iternext)(PyObject *); + iterable = PySequence_Fast(iterable, "argument must be iterable"); + if (!iterable) { + return -1; + } - /* Special cases: - 1) lists and tuples which can use PySequence_Fast ops - 2) extending self to self requires making a copy first - */ - if (PyList_CheckExact(iterable) || PyTuple_CheckExact(iterable) || - (PyObject *)self == iterable) { - PyObject **src, **dest; - iterable = PySequence_Fast(iterable, "argument must be iterable"); - if (!iterable) - return NULL; - n = PySequence_Fast_GET_SIZE(iterable); - if (n == 0) { - /* short circuit when iterable is empty */ - Py_DECREF(iterable); - Py_RETURN_NONE; - } - m = Py_SIZE(self); - /* It should not be possible to allocate a list large enough to cause - an overflow on any relevant platform */ - assert(m < PY_SSIZE_T_MAX - n); - if (self->ob_item == NULL) { - if (list_preallocate_exact(self, n) < 0) { - return NULL; - } - Py_SET_SIZE(self, n); - } - else if (list_resize(self, m + n) < 0) { - Py_DECREF(iterable); - return NULL; - } - /* note that we may still have self == iterable here for the - * situation a.extend(a), but the following code works - * in that case too. Just make sure to resize self - * before calling PySequence_Fast_ITEMS. - */ - /* populate the end of self with iterable's items */ - src = PySequence_Fast_ITEMS(iterable); - dest = self->ob_item + m; - for (i = 0; i < n; i++) { - PyObject *o = src[i]; - dest[i] = Py_NewRef(o); + Py_ssize_t n = PySequence_Fast_GET_SIZE(iterable); // size of iterable + if (n == 0) { + /* short circuit when iterable is empty */ + Py_DECREF(iterable); + return 0; + } + + Py_ssize_t m = Py_SIZE(self); // size of self + // It should not be possible to allocate a list large enough to cause + // an overflow on any relevant platform. + assert(m < PY_SSIZE_T_MAX - n); + if (self->ob_item == NULL) { + if (list_preallocate_exact(self, n) < 0) { + return -1; } + Py_SET_SIZE(self, n); + } + else if (list_resize(self, m + n) < 0) { Py_DECREF(iterable); - Py_RETURN_NONE; + return -1; } - it = PyObject_GetIter(iterable); - if (it == NULL) - return NULL; - iternext = *Py_TYPE(it)->tp_iternext; + // note that we may still have self == iterable here for the + // situation a.extend(a), but the following code works + // in that case too. Just make sure to resize self + // before calling PySequence_Fast_ITEMS. + // + // populate the end of self with iterable's items. + PyObject **src = PySequence_Fast_ITEMS(iterable); + PyObject **dest = self->ob_item + m; + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *o = src[i]; + dest[i] = Py_NewRef(o); + } + Py_DECREF(iterable); + return 0; +} + +static int +list_extend_iter(PyListObject *self, PyObject *iterable) +{ + + PyObject *it = PyObject_GetIter(iterable); // iter(v) + if (it == NULL) { + return -1; + } + PyObject *(*iternext)(PyObject *) = *Py_TYPE(it)->tp_iternext; /* Guess a result list size. */ - n = PyObject_LengthHint(iterable, 8); + Py_ssize_t n = PyObject_LengthHint(iterable, 8); // guess iterable size if (n < 0) { Py_DECREF(it); - return NULL; + return -1; } - m = Py_SIZE(self); + + Py_ssize_t m = Py_SIZE(self); /* size of self */ if (m > PY_SSIZE_T_MAX - n) { /* m + n overflowed; on the chance that n lied, and there really * is enough room, ignore it. If n was telling the truth, we'll @@ -972,28 +967,90 @@ list_extend(PyListObject *self, PyObject *iterable) } Py_DECREF(it); - Py_RETURN_NONE; + return 0; error: Py_DECREF(it); - return NULL; + return -1; +} + +static int +list_extend(PyListObject *self, PyObject *iterable) +{ + // Special cases: + // 1) lists and tuples which can use PySequence_Fast ops + // 2) extending self to self requires making a copy first + if (PyList_CheckExact(iterable) + || PyTuple_CheckExact(iterable) + || (PyObject *)self == iterable) + { + return list_extend_fast(self, iterable); + } + else { + return list_extend_iter(self, iterable); + } +} + +static PyObject * +list_extend_method(PyListObject *self, PyObject *iterable) +{ + if (list_extend(self, iterable) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +/*[clinic input] +list.extend as py_list_extend + + iterable: object + / + +Extend list by appending elements from the iterable. +[clinic start generated code]*/ + +static PyObject * +py_list_extend(PyListObject *self, PyObject *iterable) +/*[clinic end generated code: output=b8e0bff0ceae2abd input=9a8376a8633ed3ba]*/ +{ + return list_extend_method(self, iterable); } PyObject * _PyList_Extend(PyListObject *self, PyObject *iterable) { - return list_extend(self, iterable); + return list_extend_method(self, iterable); +} + +int +PyList_Extend(PyObject *self, PyObject *iterable) +{ + if (!PyList_Check(self)) { + PyErr_Format(PyExc_TypeError, "expected list, got %s", + Py_TYPE(self)->tp_name); + return -1; + } + return list_extend((PyListObject*)self, iterable); +} + +int +PyList_Clear(PyObject *self) +{ + if (!PyList_Check(self)) { + PyErr_Format(PyExc_TypeError, "expected list, got %s", + Py_TYPE(self)->tp_name); + return -1; + } + list_clear((PyListObject*)self); + return 0; } static PyObject * list_inplace_concat(PyListObject *self, PyObject *other) { - PyObject *result; - - result = list_extend(self, other); - if (result == NULL) - return result; - Py_DECREF(result); + if (list_extend(self, other) < 0) { + return NULL; + } return Py_NewRef(self); } @@ -1032,7 +1089,8 @@ list_pop_impl(PyListObject *self, Py_ssize_t index) const Py_ssize_t size_after_pop = Py_SIZE(self) - 1; if (size_after_pop == 0) { Py_INCREF(v); - status = _list_clear(self); + list_clear(self); + status = 0; } else { if ((size_after_pop - index) > 0) { @@ -2501,7 +2559,7 @@ list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse) self->ob_item = saved_ob_item; self->allocated = saved_allocated; if (final_ob_item != NULL) { - /* we cannot use _list_clear() for this because it does not + /* we cannot use list_clear() for this because it does not guarantee that the list is really empty when it returns */ while (--i >= 0) { Py_XDECREF(final_ob_item[i]); @@ -2789,13 +2847,12 @@ list___init___impl(PyListObject *self, PyObject *iterable) /* Empty previous contents */ if (self->ob_item != NULL) { - (void)_list_clear(self); + list_clear(self); } if (iterable != NULL) { - PyObject *rv = list_extend(self, iterable); - if (rv == NULL) + if (list_extend(self, iterable) < 0) { return -1; - Py_DECREF(rv); + } } return 0; } @@ -2849,11 +2906,11 @@ static PyMethodDef list_methods[] = { PyDoc_STR("__getitem__($self, index, /)\n--\n\nReturn self[index].")}, LIST___REVERSED___METHODDEF LIST___SIZEOF___METHODDEF - LIST_CLEAR_METHODDEF + PY_LIST_CLEAR_METHODDEF LIST_COPY_METHODDEF LIST_APPEND_METHODDEF LIST_INSERT_METHODDEF - LIST_EXTEND_METHODDEF + PY_LIST_EXTEND_METHODDEF LIST_POP_METHODDEF LIST_REMOVE_METHODDEF LIST_INDEX_METHODDEF @@ -3124,7 +3181,7 @@ PyTypeObject PyList_Type = { _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_SEQUENCE, /* tp_flags */ list___init____doc__, /* tp_doc */ (traverseproc)list_traverse, /* tp_traverse */ - (inquiry)_list_clear, /* tp_clear */ + (inquiry)list_clear_slot, /* tp_clear */ list_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ list_iter, /* tp_iter */