Skip to content

Commit 542c4c4

Browse files
committed
gh-139888: Add PyTupleWriter C API
* Add _PyTuple_NewNoTrack() and _PyTuple_ResizeNoTrack() helper functions. * Modify PySequence_Tuple() to use PyTupleWriter API. * Soft deprecate _PyTuple_Resize().
1 parent e31c22d commit 542c4c4

File tree

11 files changed

+466
-47
lines changed

11 files changed

+466
-47
lines changed

Doc/c-api/tuple.rst

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,71 @@ Tuple Objects
142142
``*p`` is destroyed. On failure, returns ``-1`` and sets ``*p`` to ``NULL``, and
143143
raises :exc:`MemoryError` or :exc:`SystemError`.
144144
145+
.. deprecated:: 3.15
146+
The function is :term:`soft deprecated`,
147+
use the :ref:`PyTupleWriter API <pytuplewriter>` instead.
148+
149+
150+
.. _pytuplewriter:
151+
152+
PyTupleWriter
153+
-------------
154+
155+
The :c:type:`PyTupleWriter` API can be used to create a Python :class:`tuple`
156+
object.
157+
158+
.. versionadded:: next
159+
160+
.. c:type:: PyTupleWriter
161+
162+
A tuple writer instance.
163+
164+
The API is **not thread safe**: a writer should only be used by a single
165+
thread at the same time.
166+
167+
The instance must be destroyed by :c:func:`PyTupleWriter_Finish` on
168+
success, or :c:func:`PyTupleWriter_Discard` on error.
169+
170+
171+
.. c:function:: PyTupleWriter* PyTupleWriter_Create(Py_ssize_t size)
172+
173+
Create a :c:type:`PyTupleWriter` to add *size* items.
174+
175+
If *size* is greater than zero, preallocate *size* items. The caller is
176+
responsible to add *size* items using :c:func:`PyTupleWriter_Add`.
177+
178+
On error, set an exception and return ``NULL``.
179+
180+
*size* must be positive or zero.
181+
182+
183+
.. c:function:: PyObject* PyTupleWriter_Finish(PyTupleWriter *writer)
184+
185+
Finish a :c:type:`PyTupleWriter` created by
186+
:c:func:`PyTupleWriter_Create`.
187+
188+
On success, return a Python :class:`tuple` object.
189+
On error, set an exception and return ``NULL``.
190+
191+
The writer instance is invalid after the call in any case.
192+
No API can be called on the writer after :c:func:`PyTupleWriter_Finish`.
193+
194+
.. c:function:: void PyTupleWriter_Discard(PyTupleWriter *writer)
195+
196+
Discard a :c:type:`PyTupleWriter` created by :c:func:`PyTupleWriter_Create`.
197+
198+
Do nothing if *writer* is ``NULL``.
199+
200+
The writer instance is invalid after the call.
201+
No API can be called on the writer after :c:func:`PyTupleWriter_Discard`.
202+
203+
.. c:function:: int PyTupleWriter_Add(PyTupleWriter *writer, PyObject *item)
204+
205+
Add an item to *writer*.
206+
207+
On success, return ``0``.
208+
On error, set an exception and return ``-1``.
209+
145210
146211
.. _struct-sequence-objects:
147212

Doc/whatsnew/3.15.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,15 @@ New features
855855
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
856856
(Contributed by Victor Stinner in :gh:`111489`.)
857857

858+
* Add a :ref:`PyTupleWriter API <pytuplewriter>` to create :class:`tuple`:
859+
860+
* :c:func:`PyTupleWriter_Create`
861+
* :c:func:`PyTupleWriter_Add`
862+
* :c:func:`PyTupleWriter_Finish`
863+
* :c:func:`PyTupleWriter_Discard`
864+
865+
(Contributed by Victor Stinner in :gh:`139888`.)
866+
858867

859868
Porting to Python 3.15
860869
----------------------

Include/cpython/tupleobject.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,14 @@ PyTuple_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) {
4242
PyAPI_FUNC(PyObject*) PyTuple_FromArray(
4343
PyObject *const *array,
4444
Py_ssize_t size);
45+
46+
// --- Public PyUnicodeWriter API --------------------------------------------
47+
48+
typedef struct PyTupleWriter PyTupleWriter;
49+
50+
PyAPI_FUNC(PyTupleWriter*) PyTupleWriter_Create(Py_ssize_t size);
51+
PyAPI_FUNC(int) PyTupleWriter_Add(
52+
PyTupleWriter *writer,
53+
PyObject *item);
54+
PyAPI_FUNC(PyObject*) PyTupleWriter_Finish(PyTupleWriter *writer);
55+
PyAPI_FUNC(void) PyTupleWriter_Discard(PyTupleWriter *writer);

Include/internal/pycore_freelist_state.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extern "C" {
2828
# define Py_object_stack_chunks_MAXFREELIST 4
2929
# define Py_unicode_writers_MAXFREELIST 1
3030
# define Py_bytes_writers_MAXFREELIST 1
31+
# define Py_tuple_writers_MAXFREELIST 1
3132
# define Py_pycfunctionobject_MAXFREELIST 16
3233
# define Py_pycmethodobject_MAXFREELIST 16
3334
# define Py_pymethodobjects_MAXFREELIST 20
@@ -63,6 +64,7 @@ struct _Py_freelists {
6364
struct _Py_freelist object_stack_chunks;
6465
struct _Py_freelist unicode_writers;
6566
struct _Py_freelist bytes_writers;
67+
struct _Py_freelist tuple_writers;
6668
struct _Py_freelist pycfunctionobject;
6769
struct _Py_freelist pycmethodobject;
6870
struct _Py_freelist pymethodobjects;

Lib/test/test_capi/test_tuple.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,5 +302,30 @@ def my_iter():
302302
self.assertEqual(tuples, [])
303303

304304

305+
class TupleWriterTest(unittest.TestCase):
306+
def create_writer(self, size):
307+
return _testcapi.PyTupleWriter(size)
308+
309+
def test_create(self):
310+
# Test PyTupleWriter_Create()
311+
writer = self.create_writer(0)
312+
self.assertIs(writer.finish(), ())
313+
314+
writer = self.create_writer(123)
315+
self.assertIs(writer.finish(), ())
316+
317+
def test_add(self):
318+
# Test PyTupleWriter_Add()
319+
writer = self.create_writer(3)
320+
for ch in 'abc':
321+
writer.add(ch)
322+
self.assertEqual(writer.finish(), ('a', 'b', 'c'))
323+
324+
writer = self.create_writer(0)
325+
for i in range(1024):
326+
writer.add(i)
327+
self.assertEqual(writer.finish(), tuple(range(1024)))
328+
329+
305330
if __name__ == "__main__":
306331
unittest.main()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Add a :ref:`PyTupleWriter API <pytuplewriter>` to create :class:`tuple`:
2+
3+
* :c:func:`PyTupleWriter_Create`
4+
* :c:func:`PyTupleWriter_Add`
5+
* :c:func:`PyTupleWriter_Finish`
6+
* :c:func:`PyTupleWriter_Discard`
7+
8+
Patch by Victor Stinner.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The :c:func:`_PyTuple_Resize` function is now :term:`soft deprecated`, use
2+
the :ref:`PyTupleWriter API <pytuplewriter>` instead.

Modules/_testcapi/tuple.c

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,127 @@ tuple_fromarray(PyObject* Py_UNUSED(module), PyObject *args)
131131
}
132132

133133

134+
// --- PyTupleWriter type ---------------------------------------------------
135+
136+
typedef struct {
137+
PyObject_HEAD
138+
PyTupleWriter *writer;
139+
} WriterObject;
140+
141+
142+
static PyObject *
143+
writer_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
144+
{
145+
WriterObject *self = (WriterObject *)type->tp_alloc(type, 0);
146+
if (!self) {
147+
return NULL;
148+
}
149+
self->writer = NULL;
150+
return (PyObject*)self;
151+
}
152+
153+
154+
static int
155+
writer_init(PyObject *self_raw, PyObject *args, PyObject *kwargs)
156+
{
157+
if (kwargs && PyDict_GET_SIZE(kwargs)) {
158+
PyErr_Format(PyExc_TypeError,
159+
"PyTupleWriter() takes exactly no keyword arguments");
160+
return -1;
161+
}
162+
163+
Py_ssize_t size;
164+
if (!PyArg_ParseTuple(args, "n", &size)) {
165+
return -1;
166+
}
167+
168+
WriterObject *self = (WriterObject *)self_raw;
169+
if (self->writer) {
170+
PyTupleWriter_Discard(self->writer);
171+
}
172+
self->writer = PyTupleWriter_Create(size);
173+
if (self->writer == NULL) {
174+
return -1;
175+
}
176+
return 0;
177+
}
178+
179+
180+
static void
181+
writer_dealloc(PyObject *self_raw)
182+
{
183+
WriterObject *self = (WriterObject *)self_raw;
184+
PyTypeObject *tp = Py_TYPE(self);
185+
if (self->writer) {
186+
PyTupleWriter_Discard(self->writer);
187+
}
188+
tp->tp_free(self);
189+
Py_DECREF(tp);
190+
}
191+
192+
193+
static inline int
194+
writer_check(WriterObject *self)
195+
{
196+
if (self->writer == NULL) {
197+
PyErr_SetString(PyExc_ValueError, "operation on finished writer");
198+
return -1;
199+
}
200+
return 0;
201+
}
202+
203+
204+
static PyObject*
205+
writer_add(PyObject *self_raw, PyObject *item)
206+
{
207+
WriterObject *self = (WriterObject *)self_raw;
208+
if (writer_check(self) < 0) {
209+
return NULL;
210+
}
211+
212+
if (PyTupleWriter_Add(self->writer, item) < 0) {
213+
return NULL;
214+
}
215+
Py_RETURN_NONE;
216+
}
217+
218+
219+
static PyObject*
220+
writer_finish(PyObject *self_raw, PyObject *Py_UNUSED(args))
221+
{
222+
WriterObject *self = (WriterObject *)self_raw;
223+
if (writer_check(self) < 0) {
224+
return NULL;
225+
}
226+
227+
PyObject *tuple = PyTupleWriter_Finish(self->writer);
228+
self->writer = NULL;
229+
return tuple;
230+
}
231+
232+
233+
static PyMethodDef writer_methods[] = {
234+
{"add", _PyCFunction_CAST(writer_add), METH_O},
235+
{"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS},
236+
{NULL, NULL} /* sentinel */
237+
};
238+
239+
static PyType_Slot Writer_Type_slots[] = {
240+
{Py_tp_new, writer_new},
241+
{Py_tp_init, writer_init},
242+
{Py_tp_dealloc, writer_dealloc},
243+
{Py_tp_methods, writer_methods},
244+
{0, 0}, /* sentinel */
245+
};
246+
247+
static PyType_Spec Writer_spec = {
248+
.name = "_testcapi.PyTupleWriter",
249+
.basicsize = sizeof(WriterObject),
250+
.flags = Py_TPFLAGS_DEFAULT,
251+
.slots = Writer_Type_slots,
252+
};
253+
254+
134255
static PyMethodDef test_methods[] = {
135256
{"tuple_get_size", tuple_get_size, METH_O},
136257
{"tuple_get_item", tuple_get_item, METH_VARARGS},
@@ -148,5 +269,15 @@ _PyTestCapi_Init_Tuple(PyObject *m)
148269
return -1;
149270
}
150271

272+
PyTypeObject *writer_type = (PyTypeObject *)PyType_FromSpec(&Writer_spec);
273+
if (writer_type == NULL) {
274+
return -1;
275+
}
276+
if (PyModule_AddType(m, writer_type) < 0) {
277+
Py_DECREF(writer_type);
278+
return -1;
279+
}
280+
Py_DECREF(writer_type);
281+
151282
return 0;
152283
}

Objects/abstract.c

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,8 +1996,6 @@ PySequence_DelSlice(PyObject *s, Py_ssize_t i1, Py_ssize_t i2)
19961996
PyObject *
19971997
PySequence_Tuple(PyObject *v)
19981998
{
1999-
PyObject *it; /* iter(v) */
2000-
20011999
if (v == NULL) {
20022000
return null_error();
20032001
}
@@ -2010,62 +2008,39 @@ PySequence_Tuple(PyObject *v)
20102008
a copy, so there's no need for exactness below. */
20112009
return Py_NewRef(v);
20122010
}
2013-
if (PyList_CheckExact(v))
2011+
if (PyList_CheckExact(v)) {
20142012
return PyList_AsTuple(v);
2013+
}
20152014

20162015
/* Get iterator. */
2017-
it = PyObject_GetIter(v);
2018-
if (it == NULL)
2016+
PyObject *it = PyObject_GetIter(v);
2017+
if (it == NULL) {
20192018
return NULL;
2020-
2021-
Py_ssize_t n;
2022-
PyObject *buffer[8];
2023-
for (n = 0; n < 8; n++) {
2024-
PyObject *item = PyIter_Next(it);
2025-
if (item == NULL) {
2026-
if (PyErr_Occurred()) {
2027-
goto fail;
2028-
}
2029-
Py_DECREF(it);
2030-
return _PyTuple_FromArraySteal(buffer, n);
2031-
}
2032-
buffer[n] = item;
20332019
}
2034-
PyListObject *list = (PyListObject *)PyList_New(16);
2035-
if (list == NULL) {
2020+
2021+
PyTupleWriter *writer = PyTupleWriter_Create(0);
2022+
if (writer == NULL) {
20362023
goto fail;
20372024
}
2038-
assert(n == 8);
2039-
Py_SET_SIZE(list, n);
2040-
for (Py_ssize_t j = 0; j < n; j++) {
2041-
PyList_SET_ITEM(list, j, buffer[j]);
2042-
}
2025+
20432026
for (;;) {
20442027
PyObject *item = PyIter_Next(it);
20452028
if (item == NULL) {
20462029
if (PyErr_Occurred()) {
2047-
Py_DECREF(list);
2048-
Py_DECREF(it);
2049-
return NULL;
2030+
goto fail;
20502031
}
20512032
break;
20522033
}
2053-
if (_PyList_AppendTakeRef(list, item) < 0) {
2054-
Py_DECREF(list);
2055-
Py_DECREF(it);
2056-
return NULL;
2034+
if (PyTupleWriter_Add(writer, item) < 0) {
2035+
goto fail;
20572036
}
20582037
}
20592038
Py_DECREF(it);
2060-
PyObject *res = _PyList_AsTupleAndClear(list);
2061-
Py_DECREF(list);
2062-
return res;
2039+
return PyTupleWriter_Finish(writer);
2040+
20632041
fail:
20642042
Py_DECREF(it);
2065-
while (n > 0) {
2066-
n--;
2067-
Py_DECREF(buffer[n]);
2068-
}
2043+
PyTupleWriter_Discard(writer);
20692044
return NULL;
20702045
}
20712046

Objects/object.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,7 @@ _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization)
946946
}
947947
clear_freelist(&freelists->unicode_writers, is_finalization, PyMem_Free);
948948
clear_freelist(&freelists->bytes_writers, is_finalization, PyMem_Free);
949+
clear_freelist(&freelists->tuple_writers, is_finalization, PyMem_Free);
949950
clear_freelist(&freelists->ints, is_finalization, free_object);
950951
clear_freelist(&freelists->pycfunctionobject, is_finalization, PyObject_GC_Del);
951952
clear_freelist(&freelists->pycmethodobject, is_finalization, PyObject_GC_Del);

0 commit comments

Comments
 (0)