diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 59e7a31bc2ef06..c15f82603aa944 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -582,6 +582,7 @@ func,PySequence_Contains,3.2,, func,PySequence_Count,3.2,, func,PySequence_DelItem,3.2,, func,PySequence_DelSlice,3.2,, +func,PySequence_Fast,3.2,, func,PySequence_GetItem,3.2,, func,PySequence_GetSlice,3.2,, func,PySequence_In,3.2,, diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 2402fb23c86b85..fce1eecd23cd32 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -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`.) diff --git a/Include/abstract.h b/Include/abstract.h index 4efe4fcb014903..72e7dabcb24a9f 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -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. diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index 8fed1d3110988b..1ae92dff3aab7e 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -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))) diff --git a/Lib/test/test_capi/test_abstract.py b/Lib/test/test_capi/test_abstract.py index 3de251bc5c241e..123dbf02badad8 100644 --- a/Lib/test/test_capi/test_abstract.py +++ b/Lib/test/test_capi/test_abstract.py @@ -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 diff --git a/Misc/NEWS.d/next/C_API/2025-03-07-14-49-06.gh-issue-130947._Pw0IX.rst b/Misc/NEWS.d/next/C_API/2025-03-07-14-49-06.gh-issue-130947._Pw0IX.rst new file mode 100644 index 00000000000000..f78aa1e7c4abe6 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-03-07-14-49-06.gh-issue-130947._Pw0IX.rst @@ -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. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 9317be605f0065..276526a1b6908e 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -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] diff --git a/Modules/_testlimitedcapi/abstract.c b/Modules/_testlimitedcapi/abstract.c index 6056dd100d6069..087c2aa7379fcc 100644 --- a/Modules/_testlimitedcapi/abstract.c +++ b/Modules/_testlimitedcapi/abstract.c @@ -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}, @@ -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}, };