Skip to content

Commit bc749d9

Browse files
committed
pythongh-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().
1 parent ded59f7 commit bc749d9

13 files changed

Lines changed: 749 additions & 52 deletions

File tree

‎Doc/c-api/tuple.rst‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,82 @@ 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+
Use the :ref:`PyTupleWriter API <pytuplewriter>` instead.
147+
148+
149+
.. _pytuplewriter:
150+
151+
PyTupleWriter
152+
-------------
153+
154+
The :c:type:`PyTupleWriter` API can be used to create a Python :class:`tuple`
155+
object.
156+
157+
.. versionadded:: next
158+
159+
.. c:type:: PyTupleWriter
160+
161+
A tuple writer instance.
162+
163+
The API is **not thread safe**: a writer should only be used by a single
164+
thread at the same time.
165+
166+
The instance must be destroyed by :c:func:`PyTupleWriter_Finish` on
167+
success, or :c:func:`PyTupleWriter_Discard` on error.
168+
169+
170+
.. c:function:: PyTupleWriter* PyTupleWriter_Create(Py_ssize_t size)
171+
172+
Create a :c:type:`PyTupleWriter` to add *size* items.
173+
174+
If *size* is greater than zero, preallocate *size* items. The caller is
175+
responsible to add *size* items using :c:func:`PyTupleWriter_Add`.
176+
177+
On error, set an exception and return ``NULL``.
178+
179+
*size* must be positive or zero.
180+
181+
182+
.. c:function:: PyObject* PyTupleWriter_Finish(PyTupleWriter *writer)
183+
184+
Finish a :c:type:`PyTupleWriter` created by
185+
:c:func:`PyTupleWriter_Create`.
186+
187+
On success, return a Python :class:`tuple` object.
188+
On error, set an exception and return ``NULL``.
189+
190+
The writer instance is invalid after the call in any case.
191+
No API can be called on the writer after :c:func:`PyTupleWriter_Finish`.
192+
193+
.. c:function:: void PyTupleWriter_Discard(PyTupleWriter *writer)
194+
195+
Discard a :c:type:`PyTupleWriter` created by :c:func:`PyTupleWriter_Create`.
196+
197+
Do nothing if *writer* is ``NULL``.
198+
199+
The writer instance is invalid after the call.
200+
No API can be called on the writer after :c:func:`PyTupleWriter_Discard`.
201+
202+
.. c:function:: int PyTupleWriter_Add(PyTupleWriter *writer, PyObject *item)
203+
204+
Add an item to *writer*.
205+
206+
* On success, return ``0``.
207+
* If *item* is ``NULL`` with an exception set, return ``-1``.
208+
* On error, set an exception and return ``-1``.
209+
210+
.. c:function:: int PyTupleWriter_AddSteal(PyTupleWriter *writer, PyObject *item)
211+
212+
Similar to :c:func:`PyTupleWriter_Add`, but take the ownership of *item*.
213+
214+
.. c:function:: int PyTupleWriter_AddArray(PyTupleWriter *writer, PyObject *const *array, Py_ssize_t size)
215+
216+
Add items from an array to *writer*.
217+
218+
On success, return ``0``.
219+
On error, set an exception and return ``-1``.
220+
145221
146222
.. _struct-sequence-objects:
147223

‎Doc/whatsnew/3.15.rst‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,17 @@ New features
887887
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
888888
(Contributed by Victor Stinner in :gh:`111489`.)
889889

890+
* Add a :ref:`PyTupleWriter API <pytuplewriter>` to create :class:`tuple`:
891+
892+
* :c:func:`PyTupleWriter_Create`
893+
* :c:func:`PyTupleWriter_Add`
894+
* :c:func:`PyTupleWriter_AddSteal`
895+
* :c:func:`PyTupleWriter_AddArray`
896+
* :c:func:`PyTupleWriter_Finish`
897+
* :c:func:`PyTupleWriter_Discard`
898+
899+
(Contributed by Victor Stinner in :gh:`139888`.)
900+
890901

891902
Porting to Python 3.15
892903
----------------------
@@ -1017,6 +1028,9 @@ Deprecated C APIs
10171028
since 3.15 and will be removed in 3.17.
10181029
(Contributed by Nikita Sobolev in :gh:`136355`.)
10191030

1031+
* Deprecate the :c:func:`_PyTuple_Resize` function:
1032+
use the new :ref:`PyTupleWriter API <pytuplewriter>` instead.
1033+
(Contributed by Victor Stinner in :gh:`139888`.)
10201034

10211035
.. Add C API deprecations above alphabetically, not here at the end.
10221036

‎Include/cpython/tupleobject.h‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ typedef struct {
1212
PyObject *ob_item[1];
1313
} PyTupleObject;
1414

15-
PyAPI_FUNC(int) _PyTuple_Resize(PyObject **, Py_ssize_t);
15+
_Py_DEPRECATED_EXTERNALLY(3.15) PyAPI_FUNC(int) _PyTuple_Resize(PyObject **, Py_ssize_t);
1616

1717
/* Cast argument to PyTupleObject* type. */
1818
#define _PyTuple_CAST(op) \
@@ -42,3 +42,23 @@ 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 {
49+
char _reserved[sizeof(Py_uintptr_t) * 20];
50+
} PyTupleWriter;
51+
52+
PyAPI_FUNC(int) PyTupleWriter_Init(PyTupleWriter *writer, Py_ssize_t size);
53+
PyAPI_FUNC(int) PyTupleWriter_Add(
54+
PyTupleWriter *writer,
55+
PyObject *item);
56+
PyAPI_FUNC(int) PyTupleWriter_AddSteal(
57+
PyTupleWriter *writer,
58+
PyObject *item);
59+
PyAPI_FUNC(int) PyTupleWriter_AddArray(
60+
PyTupleWriter *writer,
61+
PyObject *const *array,
62+
Py_ssize_t size);
63+
PyAPI_FUNC(PyObject*) PyTupleWriter_Finish(PyTupleWriter *writer);
64+
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;

‎Include/internal/pycore_tuple.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ _PyTuple_Recycle(PyObject *op)
6868
#endif
6969
#define _PyTuple_HASH_EMPTY (_PyTuple_HASH_XXPRIME_5 + (_PyTuple_HASH_XXPRIME_5 ^ 3527539UL))
7070

71+
extern PyObject** _PyTupleWriter_GetItems(
72+
PyTupleWriter *writer,
73+
Py_ssize_t *size);
74+
7175
#ifdef __cplusplus
7276
}
7377
#endif

‎Lib/test/test_capi/test_tuple.py‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,5 +302,71 @@ 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+
with self.assertRaises(SystemError):
318+
self.create_writer(-2)
319+
320+
def check_add(self, name):
321+
writer = self.create_writer(3)
322+
add = getattr(writer, name)
323+
for ch in 'abc':
324+
add(ch)
325+
self.assertEqual(writer.finish(), ('a', 'b', 'c'))
326+
327+
writer = self.create_writer(0)
328+
add = getattr(writer, name)
329+
for i in range(1024):
330+
add(i)
331+
self.assertEqual(writer.finish(), tuple(range(1024)))
332+
333+
writer = self.create_writer(1)
334+
add = getattr(writer, name)
335+
with self.assertRaises(SystemError):
336+
add(NULL)
337+
338+
def test_add(self):
339+
# Test PyTupleWriter_Add()
340+
self.check_add('add')
341+
342+
def test_add_steal(self):
343+
# Test PyTupleWriter_AddSteal()
344+
self.check_add('add_steal')
345+
346+
def test_add_array(self):
347+
writer = self.create_writer(0)
348+
writer.add_array(('a', 'b', 'c'))
349+
writer.add_array(('d', 'e'))
350+
self.assertEqual(writer.finish(), ('a', 'b', 'c', 'd', 'e'))
351+
352+
writer = self.create_writer(0)
353+
writer.add_array(tuple(range(1024)))
354+
self.assertEqual(writer.finish(), tuple(range(1024)))
355+
356+
def test_discard(self):
357+
# test the small_tuple buffer (16 items)
358+
writer = self.create_writer(3)
359+
writer.add(object())
360+
writer.add(object())
361+
# must not leak references
362+
writer.discard()
363+
364+
# test the tuple code path (17 items or more)
365+
writer = self.create_writer(1024)
366+
writer.add_array(tuple(range(1024)))
367+
# must not leak references
368+
writer.discard()
369+
370+
305371
if __name__ == "__main__":
306372
unittest.main()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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_AddSteal`
6+
* :c:func:`PyTupleWriter_AddArray`
7+
* :c:func:`PyTupleWriter_Finish`
8+
* :c:func:`PyTupleWriter_Discard`
9+
10+
Patch by Victor Stinner.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Deprecate the :c:func:`_PyTuple_Resize` function: use the :ref:`PyTupleWriter
2+
API <pytuplewriter>` instead.

0 commit comments

Comments
 (0)