From 10cbd1fe88d1095a03cce24fb126d479668a67c3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 13 Mar 2025 13:00:57 +0100 Subject: gh-130947: Add again PySequence_Fast() to the limited C API (#130948) Add again PySequence_Fast() to the limited C API. Add unit tests. --- Doc/data/stable_abi.dat | 1 + Doc/whatsnew/3.14.rst | 7 +++-- Include/abstract.h | 9 ++++++ Include/cpython/abstract.h | 9 ------ Lib/test/test_capi/test_abstract.py | 36 ++++++++++++++++++++++ .../2025-03-07-14-49-06.gh-issue-130947._Pw0IX.rst | 2 ++ Misc/stable_abi.toml | 1 - Modules/_testcapi/abstract.c | 24 +++++++++++++++ Modules/_testlimitedcapi/abstract.c | 14 +++++++++ 9 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2025-03-07-14-49-06.gh-issue-130947._Pw0IX.rst diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 59e7a31..c15f826 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 28879c4..8f58901 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1598,9 +1598,10 @@ 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_GET_SIZE`, + :c:macro:`PySequence_Fast_GET_ITEM` and :c:macro:`PySequence_Fast_ITEMS` + macros from the limited C API, since these macros never worked in the limited + C API. Keep :c:func:`PySequence_Fast` in the limited C API. (Contributed by Victor Stinner in :gh:`91417`.) diff --git a/Include/abstract.h b/Include/abstract.h index 4efe4fc..b9199fc 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -726,6 +726,15 @@ 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 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 8fed1d3..ffd19cc 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -86,15 +86,6 @@ 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. */ #define PySequence_Fast_GET_SIZE(o) \ diff --git a/Lib/test/test_capi/test_abstract.py b/Lib/test/test_capi/test_abstract.py index 3de251b..912c2de 100644 --- a/Lib/test/test_capi/test_abstract.py +++ b/Lib/test/test_capi/test_abstract.py @@ -994,6 +994,42 @@ class CAPITest(unittest.TestCase): self.assertRaises(TypeError, xtuple, 42) self.assertRaises(SystemError, xtuple, NULL) + def test_sequence_fast(self): + # Test PySequence_Fast() + sequence_fast = _testlimitedcapi.sequence_fast + sequence_fast_get_size = _testcapi.sequence_fast_get_size + sequence_fast_get_item = _testcapi.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 0000000..ff983d4 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-03-07-14-49-06.gh-issue-130947._Pw0IX.rst @@ -0,0 +1,2 @@ +Add again :c:func:`PySequence_Fast` to the limited C API. +Patch by Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 9317be6..276526a 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/_testcapi/abstract.c b/Modules/_testcapi/abstract.c index 8c2c713..d4045af 100644 --- a/Modules/_testcapi/abstract.c +++ b/Modules/_testcapi/abstract.c @@ -157,6 +157,27 @@ pyiter_nextitem(PyObject *self, PyObject *iter) } +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_getoptionalattr", object_getoptionalattr, METH_VARARGS}, {"object_getoptionalattrstring", object_getoptionalattrstring, METH_VARARGS}, @@ -167,6 +188,9 @@ static PyMethodDef test_methods[] = { {"PyIter_Next", pyiter_next, METH_O}, {"PyIter_NextItem", pyiter_nextitem, METH_O}, + + {"sequence_fast_get_size", sequence_fast_get_size, METH_O}, + {"sequence_fast_get_item", sequence_fast_get_item, METH_VARARGS}, {NULL}, }; diff --git a/Modules/_testlimitedcapi/abstract.c b/Modules/_testlimitedcapi/abstract.c index 6056dd1..e107e53 100644 --- a/Modules/_testlimitedcapi/abstract.c +++ b/Modules/_testlimitedcapi/abstract.c @@ -516,6 +516,19 @@ 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 PyMethodDef test_methods[] = { {"object_repr", object_repr, METH_O}, {"object_ascii", object_ascii, METH_O}, @@ -567,6 +580,7 @@ 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}, {NULL}, }; -- cgit v0.12