Skip to content

Commit

Permalink
gh-111138: Add PyList_Extend() and PyList_Clear() functions (#111862)
Browse files Browse the repository at this point in the history
* Split list_extend() into two sub-functions: list_extend_fast() and
  list_extend_iter().
* list_inplace_concat() no longer has to call Py_DECREF() on the
  list_extend() result, since list_extend() now returns an int.
  • Loading branch information
vstinner authored Nov 13, 2023
1 parent 29af736 commit babb787
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 127 deletions.
24 changes: 24 additions & 0 deletions Doc/c-api/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,30 @@ List Objects
list is not supported.
.. c:function:: int PyList_Extend(PyObject *list, PyObject *iterable)
Extend *list* with the contents of *iterable*. This is the same as
``PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable)``
and analogous to ``list.extend(iterable)`` or ``list += iterable``.
Raise an exception and return ``-1`` if *list* is not a :class:`list`
object. Return 0 on success.
.. versionadded:: 3.13
.. c:function:: int PyList_Clear(PyObject *list)
Remove all items from *list*. This is the same as
``PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL)`` and analogous to
``list.clear()`` or ``del list[:]``.
Raise an exception and return ``-1`` if *list* is not a :class:`list`
object. Return 0 on success.
.. versionadded:: 3.13
.. c:function:: int PyList_Sort(PyObject *list)
Sort the items of *list* in place. Return ``0`` on success, ``-1`` on
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------------
Expand Down
3 changes: 3 additions & 0 deletions Include/cpython/listobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ PyList_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) {
}
#define PyList_SET_ITEM(op, index, value) \
PyList_SET_ITEM(_PyObject_CAST(op), (index), _PyObject_CAST(value))

PyAPI_FUNC(int) PyList_Extend(PyObject *self, PyObject *iterable);
PyAPI_FUNC(int) PyList_Clear(PyObject *self);
77 changes: 72 additions & 5 deletions Lib/test/test_capi/test_list.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import gc
import weakref
import unittest
import sys
from test.support import import_helper
from collections import UserList
_testcapi = import_helper.import_module('_testcapi')
Expand All @@ -12,6 +13,15 @@ class ListSubclass(list):
pass


class DelAppend:
def __init__(self, lst, item):
self.lst = lst
self.item = item

def __del__(self):
self.lst.append(self.item)


class CAPITest(unittest.TestCase):
def test_check(self):
# Test PyList_Check()
Expand Down Expand Up @@ -196,10 +206,10 @@ def test_list_getslice(self):

def test_list_setslice(self):
# Test PyList_SetSlice()
setslice = _testcapi.list_setslice
list_setslice = _testcapi.list_setslice
def set_slice(lst, low, high, value):
lst = lst.copy()
self.assertEqual(setslice(lst, low, high, value), 0)
self.assertEqual(list_setslice(lst, low, high, value), 0)
return lst

# insert items
Expand Down Expand Up @@ -231,8 +241,21 @@ def set_slice(lst, low, high, value):
self.assertEqual(set_slice(lst, 0, len(lst), NULL), [])
self.assertEqual(set_slice(lst, 3, len(lst), NULL), list("abc"))

self.assertRaises(SystemError, setslice, (), 0, 0, [])
self.assertRaises(SystemError, setslice, 42, 0, 0, [])
self.assertRaises(SystemError, list_setslice, (), 0, 0, [])
self.assertRaises(SystemError, list_setslice, 42, 0, 0, [])

# Item finalizer modify the list (clear the list)
lst = []
lst.append(DelAppend(lst, 'zombie'))
self.assertEqual(list_setslice(lst, 0, len(lst), NULL), 0)
self.assertEqual(lst, ['zombie'])

# Item finalizer modify the list (remove an list item)
lst = []
lst.append(DelAppend(lst, 'zombie'))
lst.extend("abc")
self.assertEqual(list_setslice(lst, 0, 1, NULL), 0)
self.assertEqual(lst, ['a', 'b', 'c', 'zombie'])

# CRASHES setslice(NULL, 0, 0, [])

Expand Down Expand Up @@ -275,3 +298,47 @@ 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(SystemError, list_clear, ())
self.assertRaises(SystemError, list_clear, object())

# Item finalizer modify the list
lst = []
lst.append(DelAppend(lst, 'zombie'))
list_clear(lst)
self.assertEqual(lst, ['zombie'])

# CRASHES list_clear(NULL)

def test_list_extend(self):
# Test PyList_Extend()
list_extend = _testcapi.list_extend

for other_type in (list, tuple, str, iter):
lst = list("ab")
arg = other_type("def")
self.assertEqual(list_extend(lst, arg), 0)
self.assertEqual(lst, list("abdef"))

# PyList_Extend(lst, lst)
lst = list("abc")
self.assertEqual(list_extend(lst, lst), 0)
self.assertEqual(lst, list("abcabc"))

self.assertRaises(TypeError, list_extend, [], object())
self.assertRaises(SystemError, list_extend, (), list("abc"))

# CRASHES list_extend(NULL, [])
# CRASHES list_extend([], NULL)
Original file line number Diff line number Diff line change
@@ -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.
21 changes: 21 additions & 0 deletions Modules/_testcapi/list.c
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = {
Expand All @@ -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},
};

Expand Down
20 changes: 10 additions & 10 deletions Objects/clinic/listobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit babb787

Please sign in to comment.