Repository navigation
C API: Consider adding a public PyList_Extend() function #111138
Description
Feature or enhancement
The private _PyList_Extend() function has been removed in Python 3.13: see PR #108451.
@scoder asked what is the replacement for this removed function.
The obvious replacement is PyObject_CallMethod(list, "extend", "O", arg): call the list.extend() method. But it's slower, PyObject_CallMethod() has to get the method and decode the "O" format string.
Another replacement is PyList_SetSlice(L, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, arg) which is less straightforward, but it's efficient.
I propose adding a public PyList_Extend() function. The list type is commonly used in C extensions, it's a convenient API to create a collection when the length is not known is advance.
I don't think that performance is really the problem here since PyList_SetSlice(L, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, arg) is available. The problem is more to more the function easier to discover and make the API easier to use. Also, it should help people to migrate away from the removed function.
There is already a public PyDict_Update() API for the dict type, it is part of the limited C API.
If we add a function for the list type, should we also add PySet_Update() "for completeness"? The private _PySet_Update() was removed in Python 3.13.
A code search on _PyList_Extend in PyPI top 5,000 projects (2023-07-04) found 4 projects using this function:
- Cython (0.29.36)
- catboost (1.2)
- mypy (1.4.1)
- pyrsistent (0.19.3)
Logs:
Details
PYPI-2023-07-04/catboost-1.2.tar.gz: catboost-1.2/catboost_all_src/contrib/tools/cython/Cython/Compiler/Builtin.py: BuiltinMethod("extend", "TO", "r", "__Pyx_PyList_Extend",
PYPI-2023-07-04/catboost-1.2.tar.gz: catboost-1.2/catboost_all_src/contrib/tools/cython/Cython/Compiler/ExprNodes.py: extend_func = "__Pyx_PyList_Extend"
PYPI-2023-07-04/catboost-1.2.tar.gz: catboost-1.2/catboost_all_src/contrib/tools/cython/Cython/Utility/Optimize.c: static CYTHON_INLINE int __Pyx_PyList_Extend(PyObject* L, PyObject* v) {
PYPI-2023-07-04/catboost-1.2.tar.gz: catboost-1.2/catboost_all_src/contrib/tools/cython/Cython/Utility/Optimize.c: PyObject* none = _PyList_Extend((PyListObject*)L, v);
PYPI-2023-07-04/Cython-0.29.36.tar.gz: Cython-0.29.36/Cython/Compiler/Builtin.py: BuiltinMethod("extend", "TO", "r", "__Pyx_PyList_Extend",
PYPI-2023-07-04/Cython-0.29.36.tar.gz: Cython-0.29.36/Cython/Compiler/ExprNodes.py: extend_func = "__Pyx_PyList_Extend"
PYPI-2023-07-04/Cython-0.29.36.tar.gz: Cython-0.29.36/Cython/Utility/Optimize.c: static CYTHON_INLINE int __Pyx_PyList_Extend(PyObject* L, PyObject* v) {
PYPI-2023-07-04/Cython-0.29.36.tar.gz: Cython-0.29.36/Cython/Utility/Optimize.c: PyObject* none = _PyList_Extend((PyListObject*)L, v);
PYPI-2023-07-04/mypy-1.4.1.tar.gz: mypy-1.4.1/mypyc/lib-rt/dict_ops.c: PyObject *res = _PyList_Extend((PyListObject *)list, view);
PYPI-2023-07-04/mypy-1.4.1.tar.gz: mypy-1.4.1/mypyc/lib-rt/dict_ops.c: PyObject *res = _PyList_Extend((PyListObject *)list, view);
PYPI-2023-07-04/mypy-1.4.1.tar.gz: mypy-1.4.1/mypyc/lib-rt/dict_ops.c: PyObject *res = _PyList_Extend((PyListObject *)list, view);
PYPI-2023-07-04/mypy-1.4.1.tar.gz: mypy-1.4.1/mypyc/lib-rt/list_ops.c: return _PyList_Extend((PyListObject *)o1, o2);
PYPI-2023-07-04/pyrsistent-0.19.3.tar.gz: pyrsistent-0.19.3/pvectorcmodule.c: PyObject *retVal = _PyList_Extend((PyListObject *)self->appendList, args);
Cython uses the function to implement its own __Pyx_PyList_Extend() API:
static CYTHON_INLINE int __Pyx_PyList_Extend(PyObject* L, PyObject* v) {
#if CYTHON_COMPILING_IN_CPYTHON
PyObject* none = _PyList_Extend((PyListObject*)L, v);
if (unlikely(!none))
return -1;
Py_DECREF(none);
return 0;
#else
return PyList_SetSlice(L, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, v);
#endif
}
Activity
[-]C API: Consider adding a public PyList_Extend() function to replace removed private _PyList_Extend()[/-][+]C API: Consider adding a public PyList_Extend() function[/+]serhiy-storchaka commentedon Oct 21, 2023
PyDict_Update()is not trivial.PyList_Extend()is trivial, literally one-liner.I don't mind adding it as a macro. Together with
PyList_Clear().Replace _PyList_Extend with PyList_SetSlice
musicinmybrain commentedon Oct 26, 2023
Just noting that the proposed
PyList_Extendmacro is not an exact drop-in replacement for the removed_PyList_Extend()function, since the necessary error-checking is a bit different:_PyList_Extend()returnsPyObject *, butPyList_SetSlice()returnsint.Replace _PyList_Extend with PyList_SetSlice
vstinner commentedon Oct 27, 2023
I dislike macros: they cannot be used in programming languages other than C, and in projects which only load libpython symbols (ex: vim text editor). See for example PEP 670 – Convert macros to functions in the Python C API.
vstinner commentedon Oct 27, 2023
For a method used in Python, it makes sense. For a C API, it looks inefficient to return None on success.
musicinmybrain commentedon Oct 27, 2023
I agree, and I think returning
intis the correct signature for a C API.It’s just worth pointing out that extension authors porting to Python 3.13 will need to be aware that they cannot simply search-and-replace
_PyList_Extendwith the proposedPyList_Extend, despite the similar names. tobgu/pyrsistent#284 is a good example.pythongh-111138: Add test_capi.list tests
pythongh-111138: Add test_capi.list tests
pythongh-111138: Add test_capi.list tests
pythongh-111138: Test the public PyList C API
vstinner commentedon Nov 1, 2023
First step: I wrote PR #111582 to test the existing public PyList C API.
pythongh-111138: Test the public PyList C API
pythongh-111138: Test the public PyList C API
pythongh-111138: Add PyList_Extend() and PyList_Clear() functions
pythongh-111138: Add PyList_Extend() and PyList_Clear() functions
pythongh-111138: Add PyList_Extend() and PyList_Clear() functions
vstinner commentedon Nov 8, 2023
PR gh-111562 added tests on the PyList C API.
I wrote PR gh-111862 to add PyList_Extend() and PyList_Clear() to the public C API.
pythongh-111138: Add PyList_Extend() and PyList_Clear() functions
pythongh-111138: Add PyList_Extend() and PyList_Clear() functions
gh-111138: Add PyList_Extend() and PyList_Clear() functions (#111862)
vstinner commentedon Nov 13, 2023
I close the issue.
Commit babb787 adds PyList_Extend() and PyList_Clear(). I wrote python/pythoncapi-compat#80 to add these functions to pythoncapi-compat.
@serhiy-storchaka's macros can also be used on Python 3.12 and older:
I didn't add this function.
pythongh-111138: Add PyList_Extend() and PyList_Clear() functions (py…
Fix `_PyList_Extend` for Python 3.13 (#17503)
pythongh-111138: Add PyList_Extend() and PyList_Clear() functions (py…