From e5a4e4eee4494018292d7885daaeb6b8b80583f7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 7 Oct 2025 17:00:03 +0200 Subject: [PATCH 1/2] gh-139888: Add PyTupleWriter C API (stack flavor) * Add _PyTuple_NewNoTrack() and _PyTuple_ResizeNoTrack() helper functions. * Modify PySequence_Tuple() to use PyTupleWriter API. * Soft deprecate _PyTuple_Resize(). --- Doc/c-api/tuple.rst | 77 +++++ Doc/whatsnew/3.15.rst | 14 + Include/cpython/tupleobject.h | 22 +- Include/internal/pycore_tuple.h | 4 + Lib/test/test_capi/test_tuple.py | 66 ++++ ...-10-10-09-54-28.gh-issue-139888.Z4Px61.rst | 10 + ...-10-10-09-58-57.gh-issue-139888.xCfJ3L.rst | 2 + Modules/_testcapi/tuple.c | 193 ++++++++++++ Objects/abstract.c | 58 +--- Objects/tupleobject.c | 283 +++++++++++++++++- 10 files changed, 677 insertions(+), 52 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2025-10-10-09-54-28.gh-issue-139888.Z4Px61.rst create mode 100644 Misc/NEWS.d/next/C_API/2025-10-10-09-58-57.gh-issue-139888.xCfJ3L.rst diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index 65f8334c437974..f22493b0675901 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -142,6 +142,83 @@ Tuple Objects ``*p`` is destroyed. On failure, returns ``-1`` and sets ``*p`` to ``NULL``, and raises :exc:`MemoryError` or :exc:`SystemError`. + .. deprecated:: 3.15 + Use the :ref:`PyTupleWriter API ` instead. + + +.. _pytuplewriter: + +PyTupleWriter +------------- + +The :c:type:`PyTupleWriter` API can be used to create a Python :class:`tuple` +object. + +.. versionadded:: next + +.. c:type:: PyTupleWriter + + A tuple writer instance. + + The API is **not thread safe**: a writer should only be used by a single + thread at the same time. + + The instance must be destroyed by :c:func:`PyTupleWriter_Finish` on + success, or :c:func:`PyTupleWriter_Discard` on error. + + +.. c:function:: int PyTupleWriter_Init(PyTupleWriter *writer, Py_ssize_t size) + + Initialize a :c:type:`PyTupleWriter` to add *size* items. + + If *size* is greater than zero, preallocate *size* items. The caller is + responsible to add *size* items using :c:func:`PyTupleWriter_Add`. + + On success, return ``0``. + On error, set an exception and return ``-1``. + + *size* must be positive or zero. + + +.. c:function:: PyObject* PyTupleWriter_Finish(PyTupleWriter *writer) + + Finish a :c:type:`PyTupleWriter` created by + :c:func:`PyTupleWriter_Init`. + + On success, return a Python :class:`tuple` object. + On error, set an exception and return ``NULL``. + + The writer instance is invalid after the call in any case. + No API can be called on the writer after :c:func:`PyTupleWriter_Finish`. + +.. c:function:: void PyTupleWriter_Discard(PyTupleWriter *writer) + + Discard a :c:type:`PyTupleWriter` created by :c:func:`PyTupleWriter_Init`. + + Do nothing if *writer* is ``NULL``. + + The writer instance is invalid after the call. + No API can be called on the writer after :c:func:`PyTupleWriter_Discard`. + +.. c:function:: int PyTupleWriter_Add(PyTupleWriter *writer, PyObject *item) + + Add an item to *writer*. + + * On success, return ``0``. + * If *item* is ``NULL`` with an exception set, return ``-1``. + * On error, set an exception and return ``-1``. + +.. c:function:: int PyTupleWriter_AddSteal(PyTupleWriter *writer, PyObject *item) + + Similar to :c:func:`PyTupleWriter_Add`, but take the ownership of *item*. + +.. c:function:: int PyTupleWriter_AddArray(PyTupleWriter *writer, PyObject *const *array, Py_ssize_t size) + + Add items from an array to *writer*. + + On success, return ``0``. + On error, set an exception and return ``-1``. + .. _struct-sequence-objects: diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index c5321ee9983625..3d9b3a3cda1f26 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -887,6 +887,17 @@ New features * Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array. (Contributed by Victor Stinner in :gh:`111489`.) +* Add a :ref:`PyTupleWriter API ` to create :class:`tuple`: + + * :c:func:`PyTupleWriter_Init` + * :c:func:`PyTupleWriter_Add` + * :c:func:`PyTupleWriter_AddSteal` + * :c:func:`PyTupleWriter_AddArray` + * :c:func:`PyTupleWriter_Finish` + * :c:func:`PyTupleWriter_Discard` + + (Contributed by Victor Stinner in :gh:`139888`.) + Porting to Python 3.15 ---------------------- @@ -1017,6 +1028,9 @@ Deprecated C APIs since 3.15 and will be removed in 3.17. (Contributed by Nikita Sobolev in :gh:`136355`.) +* Deprecate the :c:func:`_PyTuple_Resize` function: + use the new :ref:`PyTupleWriter API ` instead. + (Contributed by Victor Stinner in :gh:`139888`.) .. Add C API deprecations above alphabetically, not here at the end. diff --git a/Include/cpython/tupleobject.h b/Include/cpython/tupleobject.h index 888baaf3358267..182f9e63f8d2e5 100644 --- a/Include/cpython/tupleobject.h +++ b/Include/cpython/tupleobject.h @@ -12,7 +12,7 @@ typedef struct { PyObject *ob_item[1]; } PyTupleObject; -PyAPI_FUNC(int) _PyTuple_Resize(PyObject **, Py_ssize_t); +_Py_DEPRECATED_EXTERNALLY(3.15) PyAPI_FUNC(int) _PyTuple_Resize(PyObject **, Py_ssize_t); /* Cast argument to PyTupleObject* type. */ #define _PyTuple_CAST(op) \ @@ -42,3 +42,23 @@ PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) { PyAPI_FUNC(PyObject*) PyTuple_FromArray( PyObject *const *array, Py_ssize_t size); + +// --- Public PyUnicodeWriter API -------------------------------------------- + +typedef struct PyTupleWriter { + char _reserved[sizeof(Py_uintptr_t) * 20]; +} PyTupleWriter; + +PyAPI_FUNC(int) PyTupleWriter_Init(PyTupleWriter *writer, Py_ssize_t size); +PyAPI_FUNC(int) PyTupleWriter_Add( + PyTupleWriter *writer, + PyObject *item); +PyAPI_FUNC(int) PyTupleWriter_AddSteal( + PyTupleWriter *writer, + PyObject *item); +PyAPI_FUNC(int) PyTupleWriter_AddArray( + PyTupleWriter *writer, + PyObject *const *array, + Py_ssize_t size); +PyAPI_FUNC(PyObject*) PyTupleWriter_Finish(PyTupleWriter *writer); +PyAPI_FUNC(void) PyTupleWriter_Discard(PyTupleWriter *writer); diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index 46db02593ad106..5fcb5bd8cf7adc 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -68,6 +68,10 @@ _PyTuple_Recycle(PyObject *op) #endif #define _PyTuple_HASH_EMPTY (_PyTuple_HASH_XXPRIME_5 + (_PyTuple_HASH_XXPRIME_5 ^ 3527539UL)) +extern PyObject** _PyTupleWriter_GetItems( + PyTupleWriter *writer, + Py_ssize_t *size); + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index b6d6da008d0b7b..98e1c8ebb9b350 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -302,5 +302,71 @@ def my_iter(): self.assertEqual(tuples, []) +class TupleWriterTest(unittest.TestCase): + def create_writer(self, size): + return _testcapi.PyTupleWriter(size) + + def test_create(self): + # Test PyTupleWriter_Init() + writer = self.create_writer(0) + self.assertIs(writer.finish(), ()) + + writer = self.create_writer(123) + self.assertIs(writer.finish(), ()) + + with self.assertRaises(SystemError): + self.create_writer(-2) + + def check_add(self, name): + writer = self.create_writer(3) + add = getattr(writer, name) + for ch in 'abc': + add(ch) + self.assertEqual(writer.finish(), ('a', 'b', 'c')) + + writer = self.create_writer(0) + add = getattr(writer, name) + for i in range(1024): + add(i) + self.assertEqual(writer.finish(), tuple(range(1024))) + + writer = self.create_writer(1) + add = getattr(writer, name) + with self.assertRaises(SystemError): + add(NULL) + + def test_add(self): + # Test PyTupleWriter_Add() + self.check_add('add') + + def test_add_steal(self): + # Test PyTupleWriter_AddSteal() + self.check_add('add_steal') + + def test_add_array(self): + writer = self.create_writer(0) + writer.add_array(('a', 'b', 'c')) + writer.add_array(('d', 'e')) + self.assertEqual(writer.finish(), ('a', 'b', 'c', 'd', 'e')) + + writer = self.create_writer(0) + writer.add_array(tuple(range(1024))) + self.assertEqual(writer.finish(), tuple(range(1024))) + + def test_discard(self): + # test the small_tuple buffer (16 items) + writer = self.create_writer(3) + writer.add(object()) + writer.add(object()) + # must not leak references + writer.discard() + + # test the tuple code path (17 items or more) + writer = self.create_writer(1024) + writer.add_array(tuple(range(1024))) + # must not leak references + writer.discard() + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C_API/2025-10-10-09-54-28.gh-issue-139888.Z4Px61.rst b/Misc/NEWS.d/next/C_API/2025-10-10-09-54-28.gh-issue-139888.Z4Px61.rst new file mode 100644 index 00000000000000..78a3fb4d5b3123 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-10-10-09-54-28.gh-issue-139888.Z4Px61.rst @@ -0,0 +1,10 @@ +Add a :ref:`PyTupleWriter API ` to create :class:`tuple`: + +* :c:func:`PyTupleWriter_Init` +* :c:func:`PyTupleWriter_Add` +* :c:func:`PyTupleWriter_AddSteal` +* :c:func:`PyTupleWriter_AddArray` +* :c:func:`PyTupleWriter_Finish` +* :c:func:`PyTupleWriter_Discard` + +Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/C_API/2025-10-10-09-58-57.gh-issue-139888.xCfJ3L.rst b/Misc/NEWS.d/next/C_API/2025-10-10-09-58-57.gh-issue-139888.xCfJ3L.rst new file mode 100644 index 00000000000000..77bbda98441488 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-10-10-09-58-57.gh-issue-139888.xCfJ3L.rst @@ -0,0 +1,2 @@ +Deprecate the :c:func:`_PyTuple_Resize` function: use the :ref:`PyTupleWriter +API ` instead. diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 5de1c494c0a8c0..59f96e985541c7 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -84,7 +84,10 @@ _tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) NULLABLE(tup); Py_XINCREF(tup); } + _Py_COMP_DIAG_PUSH + _Py_COMP_DIAG_IGNORE_DEPR_DECLS int r = _PyTuple_Resize(&tup, newsize); + _Py_COMP_DIAG_POP if (r == -1) { assert(tup == NULL); return NULL; @@ -131,6 +134,186 @@ tuple_fromarray(PyObject* Py_UNUSED(module), PyObject *args) } +// --- PyTupleWriter type --------------------------------------------------- + +typedef struct { + PyObject_HEAD + PyTupleWriter writer; + int valid; +} WriterObject; + + +static PyObject * +writer_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + WriterObject *self = (WriterObject *)type->tp_alloc(type, 0); + if (!self) { + return NULL; + } + self->valid = 0; + return (PyObject*)self; +} + + +static int +writer_init(PyObject *self_raw, PyObject *args, PyObject *kwargs) +{ + if (kwargs && PyDict_GET_SIZE(kwargs)) { + PyErr_Format(PyExc_TypeError, + "PyTupleWriter() takes exactly no keyword arguments"); + return -1; + } + + Py_ssize_t size; + if (!PyArg_ParseTuple(args, "n", &size)) { + return -1; + } + + WriterObject *self = (WriterObject *)self_raw; + if (self->valid) { + PyTupleWriter_Discard(&self->writer); + } + if (PyTupleWriter_Init(&self->writer, size) < 0) { + return -1; + } + self->valid = 1; + return 0; +} + + +static void +writer_dealloc(PyObject *self_raw) +{ + WriterObject *self = (WriterObject *)self_raw; + PyTypeObject *tp = Py_TYPE(self); + if (self->valid) { + PyTupleWriter_Discard(&self->writer); + } + tp->tp_free(self); + Py_DECREF(tp); +} + + +static inline int +writer_check(WriterObject *self) +{ + if (!self->valid) { + PyErr_SetString(PyExc_ValueError, "operation on finished writer"); + return -1; + } + return 0; +} + + +static PyObject* +writer_add(PyObject *self_raw, PyObject *item) +{ + NULLABLE(item); + + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + if (PyTupleWriter_Add(&self->writer, item) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject* +writer_add_steal(PyObject *self_raw, PyObject *item) +{ + NULLABLE(item); + + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + if (PyTupleWriter_AddSteal(&self->writer, Py_XNewRef(item)) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject* +writer_add_array(PyObject *self_raw, PyObject *args) +{ + PyObject *tuple; + if (!PyArg_ParseTuple(args, "O!", &PyTuple_Type, &tuple)) { + return NULL; + } + + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + PyObject **array = &PyTuple_GET_ITEM(tuple, 0); + Py_ssize_t size = PyTuple_GET_SIZE(tuple); + if (PyTupleWriter_AddArray(&self->writer, array, size) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject* +writer_finish(PyObject *self_raw, PyObject *Py_UNUSED(args)) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + PyObject *tuple = PyTupleWriter_Finish(&self->writer); + self->valid = 0; + return tuple; +} + + +static PyObject* +writer_discard(PyObject *self_raw, PyObject *Py_UNUSED(args)) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + PyTupleWriter_Discard(&self->writer); + self->valid = 0; + Py_RETURN_NONE; +} + + +static PyMethodDef writer_methods[] = { + {"add", _PyCFunction_CAST(writer_add), METH_O}, + {"add_steal", _PyCFunction_CAST(writer_add_steal), METH_O}, + {"add_array", _PyCFunction_CAST(writer_add_array), METH_VARARGS}, + {"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS}, + {"discard", _PyCFunction_CAST(writer_discard), METH_NOARGS}, + {NULL, NULL} /* sentinel */ +}; + +static PyType_Slot Writer_Type_slots[] = { + {Py_tp_new, writer_new}, + {Py_tp_init, writer_init}, + {Py_tp_dealloc, writer_dealloc}, + {Py_tp_methods, writer_methods}, + {0, 0}, /* sentinel */ +}; + +static PyType_Spec Writer_spec = { + .name = "_testcapi.PyTupleWriter", + .basicsize = sizeof(WriterObject), + .flags = Py_TPFLAGS_DEFAULT, + .slots = Writer_Type_slots, +}; + + static PyMethodDef test_methods[] = { {"tuple_get_size", tuple_get_size, METH_O}, {"tuple_get_item", tuple_get_item, METH_VARARGS}, @@ -148,5 +331,15 @@ _PyTestCapi_Init_Tuple(PyObject *m) return -1; } + PyTypeObject *writer_type = (PyTypeObject *)PyType_FromSpec(&Writer_spec); + if (writer_type == NULL) { + return -1; + } + if (PyModule_AddType(m, writer_type) < 0) { + Py_DECREF(writer_type); + return -1; + } + Py_DECREF(writer_type); + return 0; } diff --git a/Objects/abstract.c b/Objects/abstract.c index 8adad8407d04d4..e9fdcdf9c7c95e 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1996,8 +1996,6 @@ PySequence_DelSlice(PyObject *s, Py_ssize_t i1, Py_ssize_t i2) PyObject * PySequence_Tuple(PyObject *v) { - PyObject *it; /* iter(v) */ - if (v == NULL) { return null_error(); } @@ -2010,62 +2008,36 @@ PySequence_Tuple(PyObject *v) a copy, so there's no need for exactness below. */ return Py_NewRef(v); } - if (PyList_CheckExact(v)) + if (PyList_CheckExact(v)) { return PyList_AsTuple(v); + } /* Get iterator. */ - it = PyObject_GetIter(v); - if (it == NULL) + PyObject *it = PyObject_GetIter(v); + if (it == NULL) { return NULL; - - Py_ssize_t n; - PyObject *buffer[8]; - for (n = 0; n < 8; n++) { - PyObject *item = PyIter_Next(it); - if (item == NULL) { - if (PyErr_Occurred()) { - goto fail; - } - Py_DECREF(it); - return _PyTuple_FromArraySteal(buffer, n); - } - buffer[n] = item; } - PyListObject *list = (PyListObject *)PyList_New(16); - if (list == NULL) { + + PyTupleWriter writer; + if (PyTupleWriter_Init(&writer, 0) < 0) { goto fail; } - assert(n == 8); - Py_SET_SIZE(list, n); - for (Py_ssize_t j = 0; j < n; j++) { - PyList_SET_ITEM(list, j, buffer[j]); - } + for (;;) { - PyObject *item = PyIter_Next(it); - if (item == NULL) { - if (PyErr_Occurred()) { - Py_DECREF(list); - Py_DECREF(it); - return NULL; - } + PyObject *item; + if (PyIter_NextItem(it, &item) == 0) { break; } - if (_PyList_AppendTakeRef(list, item) < 0) { - Py_DECREF(list); - Py_DECREF(it); - return NULL; + if (PyTupleWriter_AddSteal(&writer, item) < 0) { + goto fail; } } Py_DECREF(it); - PyObject *res = _PyList_AsTupleAndClear(list); - Py_DECREF(list); - return res; + return PyTupleWriter_Finish(&writer); + fail: Py_DECREF(it); - while (n > 0) { - n--; - Py_DECREF(buffer[n]); - } + PyTupleWriter_Discard(&writer); return NULL; } diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 94b7ae7e642283..22761a5c6b44cc 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -71,8 +71,8 @@ tuple_get_empty(void) return (PyObject *)&_Py_SINGLETON(tuple_empty); } -PyObject * -PyTuple_New(Py_ssize_t size) +static PyObject * +_PyTuple_NewNoTrack(Py_ssize_t size) { PyTupleObject *op; if (size == 0) { @@ -85,10 +85,25 @@ PyTuple_New(Py_ssize_t size) for (Py_ssize_t i = 0; i < size; i++) { op->ob_item[i] = NULL; } - _PyObject_GC_TRACK(op); return (PyObject *) op; } + +PyObject * +PyTuple_New(Py_ssize_t size) +{ + if (size == 0) { + return tuple_get_empty(); + } + PyObject *op = _PyTuple_NewNoTrack(size); + if (op == NULL) { + return NULL; + } + _PyObject_GC_TRACK(op); + return op; +} + + Py_ssize_t PyTuple_Size(PyObject *op) { @@ -913,8 +928,8 @@ PyTypeObject PyTuple_Type = { efficiently. In any case, don't use this if the tuple may already be known to some other part of the code. */ -int -_PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) +static int +tuple_resize(PyObject **pv, Py_ssize_t newsize, int track) { PyTupleObject *v; PyTupleObject *sv; @@ -946,7 +961,12 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) /* The empty tuple is statically allocated so we never resize it in-place. */ Py_DECREF(v); - *pv = PyTuple_New(newsize); + if (track) { + *pv = PyTuple_New(newsize); + } + else { + *pv = _PyTuple_NewNoTrack(newsize); + } return *pv == NULL ? -1 : 0; } @@ -972,14 +992,32 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) } _Py_NewReferenceNoTotal((PyObject *) sv); /* Zero out items added by growing */ - if (newsize > oldsize) + if (newsize > oldsize) { memset(&sv->ob_item[oldsize], 0, sizeof(*sv->ob_item) * (newsize - oldsize)); + } *pv = (PyObject *) sv; - _PyObject_GC_TRACK(sv); + if (track) { + _PyObject_GC_TRACK(sv); + } return 0; } + +static int +_PyTuple_ResizeNoTrack(PyObject **pv, Py_ssize_t newsize) +{ + return tuple_resize(pv, newsize, 0); +} + + +int +_PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) +{ + return tuple_resize(pv, newsize, 1); +} + + /*********************** Tuple Iterator **************************/ #define _PyTupleIterObject_CAST(op) ((_PyTupleIterObject *)(op)) @@ -1181,3 +1219,232 @@ _PyTuple_DebugMallocStats(FILE *out) _PyObject_VAR_SIZE(&PyTuple_Type, len)); } } + + +// --- Public PyTupleWriter API ---------------------------------------------- + +typedef struct _PyTupleWriter { + PyObject* small_tuple[16]; + PyObject *tuple; + PyObject **items; + size_t size; + size_t allocated; +} _PyTupleWriter; + +static_assert(sizeof(_PyTupleWriter) <= sizeof(PyTupleWriter), + "_PyTupleWriter structure is too big"); + + +static inline void +tuplewriter_free(_PyTupleWriter *writer) +{ +#ifdef Py_DEBUG + memset(writer, 0xff, sizeof(*writer)); +#endif +} + + +static int +_PyTupleWriter_SetSize(_PyTupleWriter *writer, size_t size) +{ + assert(size >= 1); + + size += (size >> 1); // Over-allocate by 50% + + if (size > (size_t)PY_SSIZE_T_MAX) { + /* Check for overflow */ + PyErr_NoMemory(); + return -1; + } + + if (writer->tuple != NULL) { + if (_PyTuple_ResizeNoTrack(&writer->tuple, (Py_ssize_t)size) < 0) { + return -1; + } + } + else { + writer->tuple = _PyTuple_NewNoTrack((Py_ssize_t)size); + if (writer->tuple == NULL) { + return -1; + } + + if (writer->size > 0) { + memcpy(_PyTuple_ITEMS(writer->tuple), + writer->small_tuple, + writer->size * sizeof(writer->small_tuple[0])); + } + } + writer->items = _PyTuple_ITEMS(writer->tuple); + writer->allocated = size; + return 0; +} + + +int +PyTupleWriter_Init(PyTupleWriter *writer_, Py_ssize_t size) +{ + _PyTupleWriter *writer = (_PyTupleWriter *)writer_; + if (size < 0) { + PyErr_BadInternalCall(); + return -1; + } + + writer->size = 0; + + if (size != 0) { + writer->tuple = _PyTuple_NewNoTrack(size); + if (writer->tuple == NULL) { + tuplewriter_free(writer); + return -1; + } + writer->items = _PyTuple_ITEMS(writer->tuple); + writer->allocated = size; + } + else { +#ifdef Py_DEBUG + memset(writer->small_tuple, 0xff, sizeof(writer->small_tuple)); +#endif + writer->tuple = NULL; + writer->items = writer->small_tuple; + writer->allocated = Py_ARRAY_LENGTH(writer->small_tuple); + } + return 0; +} + + +int +PyTupleWriter_Add(PyTupleWriter *writer_, PyObject *item) +{ + _PyTupleWriter *writer = (_PyTupleWriter *)writer_; + if (item == NULL) { + if (!PyErr_Occurred()) { + PyErr_BadInternalCall(); + } + return -1; + } + + if (writer->size >= writer->allocated) { + if (_PyTupleWriter_SetSize(writer, writer->size + 1) < 0) { + return -1; + } + } + + writer->items[writer->size] = Py_NewRef(item); + writer->size++; + return 0; +} + + +int +PyTupleWriter_AddSteal(PyTupleWriter *writer_, PyObject *item) +{ + _PyTupleWriter *writer = (_PyTupleWriter *)writer_; + if (item == NULL) { + if (!PyErr_Occurred()) { + PyErr_BadInternalCall(); + } + return -1; + } + + if (writer->size >= writer->allocated) { + if (_PyTupleWriter_SetSize(writer, writer->size + 1) < 0) { + Py_DECREF(item); + return -1; + } + } + + writer->items[writer->size] = item; + writer->size++; + return 0; +} + + +int +PyTupleWriter_AddArray(PyTupleWriter *writer_, + PyObject *const *array, Py_ssize_t size) +{ + _PyTupleWriter *writer = (_PyTupleWriter *)writer_; + if (size < 0) { + PyErr_BadInternalCall(); + return -1; + } + + if ((writer->size + (size_t)size) > writer->allocated) { + if (_PyTupleWriter_SetSize(writer, + writer->size + (size_t)size) < 0) { + return -1; + } + } + + PyObject **items = &writer->items[writer->size]; + for (Py_ssize_t i=0; i < size; i++) { + *items++ = Py_NewRef(*array++); + } + writer->size += size; + return 0; +} + + +PyObject** +_PyTupleWriter_GetItems(PyTupleWriter *writer_, Py_ssize_t *size) +{ + _PyTupleWriter *writer = (_PyTupleWriter *)writer_; + *size = writer->size; + return writer->items; +} + + +void +PyTupleWriter_Discard(PyTupleWriter *writer_) +{ + _PyTupleWriter *writer = (_PyTupleWriter *)writer_; + if (writer == NULL) { + return; + } + + if (writer->tuple != NULL) { + Py_DECREF(writer->tuple); + } + else { + PyObject **items = writer->items; + for (size_t i=0; i < writer->size; i++) { + Py_DECREF(items[i]); + } + } + + tuplewriter_free(writer); +} + + +PyObject* +PyTupleWriter_Finish(PyTupleWriter *writer_) +{ + _PyTupleWriter *writer = (_PyTupleWriter *)writer_; + if (writer->size == 0) { + PyTupleWriter_Discard(writer_); + // return the empty tuple singleton + return PyTuple_New(0); + } + + PyObject *result; + if (writer->tuple != NULL) { + if (writer->size != writer->allocated) { + if (_PyTuple_ResizeNoTrack(&writer->tuple, + (Py_ssize_t)writer->size) < 0) { + PyTupleWriter_Discard(writer_); + return NULL; + } + } + + result = writer->tuple; + writer->tuple = NULL; + _PyObject_GC_TRACK(result); + } + else { + result = _PyTuple_FromArraySteal(writer->items, + (Py_ssize_t)writer->size); + } + + tuplewriter_free(writer); + return result; +} From b72f2e59d5252affc080387f8eeef73af8dc82e7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 17 Oct 2025 00:41:55 +0200 Subject: [PATCH 2/2] Update Include/cpython/tupleobject.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Maurycy Pawłowski-Wieroński <5383+maurycy@users.noreply.github.com> --- Include/cpython/tupleobject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/tupleobject.h b/Include/cpython/tupleobject.h index 182f9e63f8d2e5..7df3535ca6def0 100644 --- a/Include/cpython/tupleobject.h +++ b/Include/cpython/tupleobject.h @@ -43,7 +43,7 @@ PyAPI_FUNC(PyObject*) PyTuple_FromArray( PyObject *const *array, Py_ssize_t size); -// --- Public PyUnicodeWriter API -------------------------------------------- +// --- Public PyTupleWriter API -------------------------------------------- typedef struct PyTupleWriter { char _reserved[sizeof(Py_uintptr_t) * 20];