Skip to content

Commit 40be397

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

File tree

11 files changed

+747
-52
lines changed

11 files changed

+747
-52
lines changed

Doc/c-api/tuple.rst

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,83 @@ 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:: int PyTupleWriter_Init(PyTupleWriter *writer, Py_ssize_t size)
171+
172+
Initialize 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 success, return ``0``.
178+
On error, set an exception and return ``-1``.
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_Init`.
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_Init`.
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+
* If *item* is ``NULL`` with an exception set, return ``-1``.
209+
* On error, set an exception and return ``-1``.
210+
211+
.. c:function:: int PyTupleWriter_AddSteal(PyTupleWriter *writer, PyObject *item)
212+
213+
Similar to :c:func:`PyTupleWriter_Add`, but take the ownership of *item*.
214+
215+
.. c:function:: int PyTupleWriter_AddArray(PyTupleWriter *writer, PyObject *const *array, Py_ssize_t size)
216+
217+
Add items from an array to *writer*.
218+
219+
On success, return ``0``.
220+
On error, set an exception and return ``-1``.
221+
145222
146223
.. _struct-sequence-objects:
147224

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_Init`
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_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_Init()
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_Init`
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)