Skip to content

Commit

Permalink
gh-130947: Add again PySequence_Fast() to the limited C API
Browse files Browse the repository at this point in the history
Add again PySequence_Fast(), PySequence_Fast_GET_SIZE() and
PySequence_Fast_GET_ITEM() to the limited C API

Add an unit tests.
  • Loading branch information
vstinner committed Mar 7, 2025
1 parent e5527f2 commit f47daa0
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 13 deletions.
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.

5 changes: 2 additions & 3 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1576,9 +1576,8 @@ Limited C API changes
implementation details.
(Contributed by Victor Stinner in :gh:`120600` and :gh:`124127`.)

* Remove :c:func:`PySequence_Fast` from the limited C API, since this function
has to be used with :c:macro:`PySequence_Fast_GET_ITEM` which never worked
in the limited C API.
* Remove the :c:macro:`PySequence_Fast_ITEMS` macro from the limited C API,
since this macro never worked in the limited C API.
(Contributed by Victor Stinner in :gh:`91417`.)


Expand Down
19 changes: 19 additions & 0 deletions Include/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,25 @@ PyAPI_FUNC(PyObject *) PySequence_Tuple(PyObject *o);
This is equivalent to the Python expression: list(o) */
PyAPI_FUNC(PyObject *) PySequence_List(PyObject *o);

/* Return the sequence 'o' as a list, unless it's already a tuple or list.
Use PySequence_Fast_GET_ITEM to access the members of this list, and
PySequence_Fast_GET_SIZE to get its length.
Returns NULL on failure. If the object does not support iteration, raises a
TypeError exception with 'm' as the message text. */
PyAPI_FUNC(PyObject *) PySequence_Fast(PyObject *o, const char* m);

/* Return the size of the sequence 'o', assuming that 'o' was returned by
PySequence_Fast and is not NULL. */
#define PySequence_Fast_GET_SIZE(o) \
(PyList_Check(o) ? PyList_Size(o) : PyTuple_Size(o))

/* Return the 'i'-th element of the sequence 'o', assuming that o was returned
by PySequence_Fast, and that i is within bounds. */
#define PySequence_Fast_GET_ITEM(o, i)\
(PyList_Check(o) ? PyList_GetItem((o), (i)) : PyTuple_GetItem((o), (i)))

/* Return the number of occurrences on value on 'o', that is, return
the number of keys for which o[key] == value.
Expand Down
11 changes: 2 additions & 9 deletions Include/cpython/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,15 @@ PyAPI_FUNC(Py_ssize_t) PyObject_LengthHint(PyObject *o, Py_ssize_t);
#define PySequence_ITEM(o, i)\
( Py_TYPE(o)->tp_as_sequence->sq_item((o), (i)) )

/* Return the sequence 'o' as a list, unless it's already a tuple or list.
Use PySequence_Fast_GET_ITEM to access the members of this list, and
PySequence_Fast_GET_SIZE to get its length.
Returns NULL on failure. If the object does not support iteration, raises a
TypeError exception with 'm' as the message text. */
PyAPI_FUNC(PyObject *) PySequence_Fast(PyObject *o, const char* m);

/* Return the size of the sequence 'o', assuming that 'o' was returned by
PySequence_Fast and is not NULL. */
#undef PySequence_Fast_GET_SIZE // limited C API implementation
#define PySequence_Fast_GET_SIZE(o) \
(PyList_Check(o) ? PyList_GET_SIZE(o) : PyTuple_GET_SIZE(o))

/* Return the 'i'-th element of the sequence 'o', assuming that o was returned
by PySequence_Fast, and that i is within bounds. */
#undef PySequence_Fast_GET_ITEM // limited C API implementation
#define PySequence_Fast_GET_ITEM(o, i)\
(PyList_Check(o) ? PyList_GET_ITEM((o), (i)) : PyTuple_GET_ITEM((o), (i)))

Expand Down
36 changes: 36 additions & 0 deletions Lib/test/test_capi/test_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,42 @@ def test_sequence_tuple(self):
self.assertRaises(TypeError, xtuple, 42)
self.assertRaises(SystemError, xtuple, NULL)

def test_sequence_fast(self):
# Tets PySequence_Fast()
sequence_fast = _testlimitedcapi.sequence_fast
sequence_fast_get_size = _testlimitedcapi.sequence_fast_get_size
sequence_fast_get_item = _testlimitedcapi.sequence_fast_get_item

tpl = ('a', 'b', 'c')
fast = sequence_fast(tpl, "err_msg")
self.assertIs(fast, tpl)
self.assertEqual(sequence_fast_get_size(fast), 3)
self.assertEqual(sequence_fast_get_item(fast, 2), 'c')

lst = ['a', 'b', 'c']
fast = sequence_fast(lst, "err_msg")
self.assertIs(fast, lst)
self.assertEqual(sequence_fast_get_size(fast), 3)
self.assertEqual(sequence_fast_get_item(fast, 2), 'c')

it = iter(['A', 'B'])
fast = sequence_fast(it, "err_msg")
self.assertEqual(fast, ['A', 'B'])
self.assertEqual(sequence_fast_get_size(fast), 2)
self.assertEqual(sequence_fast_get_item(fast, 1), 'B')

text = 'fast'
fast = sequence_fast(text, "err_msg")
self.assertEqual(fast, ['f', 'a', 's', 't'])
self.assertEqual(sequence_fast_get_size(fast), 4)
self.assertEqual(sequence_fast_get_item(fast, 0), 'f')

self.assertRaises(TypeError, sequence_fast, 42, "err_msg")
self.assertRaises(SystemError, sequence_fast, NULL, "err_msg")

# CRASHES sequence_fast_get_size(NULL)
# CRASHES sequence_fast_get_item(NULL, 0)

def test_object_generichash(self):
# Test PyObject_GenericHash()
generichash = _testcapi.object_generichash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add again :c:func:`PySequence_Fast`, :c:macro:`PySequence_Fast_GET_SIZE()`
and :c:macro:`PySequence_Fast_GET_ITEM()` to the limited C API. Patch by
Victor Stinner.
1 change: 0 additions & 1 deletion Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,6 @@
added = '3.2'
[function.PySequence_Fast]
added = '3.2'
abi_only = true
[function.PySequence_GetItem]
added = '3.2'
[function.PySequence_GetSlice]
Expand Down
37 changes: 37 additions & 0 deletions Modules/_testlimitedcapi/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,40 @@ sequence_tuple(PyObject *self, PyObject *obj)
}


static PyObject *
sequence_fast(PyObject *self, PyObject *args)
{
PyObject *obj;
const char *err_msg;
if (!PyArg_ParseTuple(args, "Os", &obj, &err_msg)) {
return NULL;
}
NULLABLE(obj);
return PySequence_Fast(obj, err_msg);
}


static PyObject *
sequence_fast_get_size(PyObject *self, PyObject *obj)
{
NULLABLE(obj);
return PyLong_FromSsize_t(PySequence_Fast_GET_SIZE(obj));
}


static PyObject *
sequence_fast_get_item(PyObject *self, PyObject *args)
{
PyObject *obj;
Py_ssize_t index;
if (!PyArg_ParseTuple(args, "On", &obj, &index)) {
return NULL;
}
NULLABLE(obj);
return PySequence_Fast_GET_ITEM(obj, index);
}


static PyMethodDef test_methods[] = {
{"object_repr", object_repr, METH_O},
{"object_ascii", object_ascii, METH_O},
Expand Down Expand Up @@ -567,6 +601,9 @@ static PyMethodDef test_methods[] = {
{"sequence_index", sequence_index, METH_VARARGS},
{"sequence_list", sequence_list, METH_O},
{"sequence_tuple", sequence_tuple, METH_O},
{"sequence_fast", sequence_fast, METH_VARARGS},
{"sequence_fast_get_size", sequence_fast_get_size, METH_O},
{"sequence_fast_get_item", sequence_fast_get_item, METH_VARARGS},

{NULL},
};
Expand Down

0 comments on commit f47daa0

Please sign in to comment.