Skip to content

Commit

Permalink
pythongh-114329: Add PyList_GetItemRef function (pythonGH-114504)
Browse files Browse the repository at this point in the history
The new `PyList_GetItemRef` is similar to `PyList_GetItem`, but returns
a strong reference instead of a borrowed reference. Additionally, if the
passed "list" object is not a list, the function sets a `TypeError`
instead of calling `PyErr_BadInternalCall()`.
  • Loading branch information
colesbury committed Feb 2, 2024
1 parent 0e71a29 commit d0f1307
Show file tree
Hide file tree
Showing 12 changed files with 68 additions and 11 deletions.
12 changes: 10 additions & 2 deletions Doc/c-api/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,21 @@ List Objects
Similar to :c:func:`PyList_Size`, but without error checking.
.. c:function:: PyObject* PyList_GetItem(PyObject *list, Py_ssize_t index)
.. c:function:: PyObject* PyList_GetItemRef(PyObject *list, Py_ssize_t index)
Return the object at position *index* in the list pointed to by *list*. The
position must be non-negative; indexing from the end of the list is not
supported. If *index* is out of bounds (<0 or >=len(list)),
supported. If *index* is out of bounds (:code:`<0 or >=len(list)`),
return ``NULL`` and set an :exc:`IndexError` exception.
.. versionadded:: 3.13
.. c:function:: PyObject* PyList_GetItem(PyObject *list, Py_ssize_t index)
Like :c:func:`PyList_GetItemRef`, but returns a
:term:`borrowed reference` instead of a :term:`strong reference`.
.. c:function:: PyObject* PyList_GET_ITEM(PyObject *list, Py_ssize_t i)
Expand Down
4 changes: 4 additions & 0 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,10 @@ PyList_GetItem:PyObject*::0:
PyList_GetItem:PyObject*:list:0:
PyList_GetItem:Py_ssize_t:index::

PyList_GetItemRef:PyObject*::+1:
PyList_GetItemRef:PyObject*:list:0:
PyList_GetItemRef:Py_ssize_t:index::

PyList_GetSlice:PyObject*::+1:
PyList_GetSlice:PyObject*:list:0:
PyList_GetSlice:Py_ssize_t:low::
Expand Down
1 change: 1 addition & 0 deletions Doc/data/stable_abi.dat

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

4 changes: 4 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,10 @@ New Features
UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`.
(Contributed by Victor Stinner in :gh:`108314`.)

* Added :c:func:`PyList_GetItemRef` function: similar to
:c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of
a :term:`borrowed reference`.

* Add :c:func:`Py_IsFinalizing` function: check if the main Python interpreter is
:term:`shutting down <interpreter shutdown>`.
(Contributed by Victor Stinner in :gh:`108014`.)
Expand Down
1 change: 1 addition & 0 deletions Include/listobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size);
PyAPI_FUNC(Py_ssize_t) PyList_Size(PyObject *);

PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t);
PyAPI_FUNC(PyObject *) PyList_GetItemRef(PyObject *, Py_ssize_t);
PyAPI_FUNC(int) PyList_SetItem(PyObject *, Py_ssize_t, PyObject *);
PyAPI_FUNC(int) PyList_Insert(PyObject *, Py_ssize_t, PyObject *);
PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *);
Expand Down
22 changes: 13 additions & 9 deletions Lib/test/test_capi/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,23 +82,28 @@ def test_list_get_size(self):
# CRASHES size(UserList())
# CRASHES size(NULL)


def test_list_getitem(self):
# Test PyList_GetItem()
getitem = _testcapi.list_getitem
def check_list_get_item(self, getitem, exctype):
# Common test cases for PyList_GetItem() and PyList_GetItemRef()
lst = [1, 2, 3]
self.assertEqual(getitem(lst, 0), 1)
self.assertEqual(getitem(lst, 2), 3)
self.assertRaises(IndexError, getitem, lst, 3)
self.assertRaises(IndexError, getitem, lst, -1)
self.assertRaises(IndexError, getitem, lst, PY_SSIZE_T_MIN)
self.assertRaises(IndexError, getitem, lst, PY_SSIZE_T_MAX)
self.assertRaises(SystemError, getitem, 42, 1)
self.assertRaises(SystemError, getitem, (1, 2, 3), 1)
self.assertRaises(SystemError, getitem, {1: 2}, 1)

self.assertRaises(exctype, getitem, 42, 1)
self.assertRaises(exctype, getitem, (1, 2, 3), 1)
self.assertRaises(exctype, getitem, {1: 2}, 1)
# CRASHES getitem(NULL, 1)

def test_list_getitem(self):
# Test PyList_GetItem()
self.check_list_get_item(_testcapi.list_getitem, SystemError)

def test_list_get_item_ref(self):
# Test PyList_GetItemRef()
self.check_list_get_item(_testcapi.list_get_item_ref, TypeError)

def test_list_get_item(self):
# Test PyList_GET_ITEM()
get_item = _testcapi.list_get_item
Expand All @@ -112,7 +117,6 @@ def test_list_get_item(self):
# CRASHES get_item(21, 2)
# CRASHES get_item(NULL, 1)


def test_list_setitem(self):
# Test PyList_SetItem()
setitem = _testcapi.list_setitem
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_stable_abi_ctypes.py

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`PyList_GetItemRef`, which is similar to
:c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of a
:term:`borrowed reference`.
2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2487,3 +2487,5 @@
abi_only = true
[data.PyExc_IncompleteInputError]
added = '3.13'
[function.PyList_GetItemRef]
added = '3.13'
13 changes: 13 additions & 0 deletions Modules/_testcapi/list.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ list_get_item(PyObject *Py_UNUSED(module), PyObject *args)
return Py_XNewRef(PyList_GET_ITEM(obj, i));
}

static PyObject *
list_get_item_ref(PyObject *Py_UNUSED(module), PyObject *args)
{
PyObject *obj;
Py_ssize_t i;
if (!PyArg_ParseTuple(args, "On", &obj, &i)) {
return NULL;
}
NULLABLE(obj);
return PyList_GetItemRef(obj, i);
}

static PyObject *
list_setitem(PyObject *Py_UNUSED(module), PyObject *args)
{
Expand Down Expand Up @@ -191,6 +203,7 @@ static PyMethodDef test_methods[] = {
{"list_get_size", list_get_size, METH_O},
{"list_getitem", list_getitem, METH_VARARGS},
{"list_get_item", list_get_item, METH_VARARGS},
{"list_get_item_ref", list_get_item_ref, METH_VARARGS},
{"list_setitem", list_setitem, METH_VARARGS},
{"list_set_item", list_set_item, METH_VARARGS},
{"list_insert", list_insert, METH_VARARGS},
Expand Down
15 changes: 15 additions & 0 deletions Objects/listobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,21 @@ PyList_GetItem(PyObject *op, Py_ssize_t i)
return ((PyListObject *)op) -> ob_item[i];
}

PyObject *
PyList_GetItemRef(PyObject *op, Py_ssize_t i)
{
if (!PyList_Check(op)) {
PyErr_SetString(PyExc_TypeError, "expected a list");
return NULL;
}
if (!valid_index(i, Py_SIZE(op))) {
_Py_DECLARE_STR(list_err, "list index out of range");
PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err));
return NULL;
}
return Py_NewRef(PyList_GET_ITEM(op, i));
}

int
PyList_SetItem(PyObject *op, Py_ssize_t i,
PyObject *newitem)
Expand Down
1 change: 1 addition & 0 deletions PC/python3dll.c

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

0 comments on commit d0f1307

Please sign in to comment.