Skip to content
Draft
76 changes: 76 additions & 0 deletions Doc/c-api/tuple.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,82 @@ 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 <pytuplewriter>` 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:: PyTupleWriter* PyTupleWriter_Create(Py_ssize_t size)
Create 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 error, set an exception and return ``NULL``.
*size* must be positive or zero.
.. c:function:: PyObject* PyTupleWriter_Finish(PyTupleWriter *writer)
Finish a :c:type:`PyTupleWriter` created by
:c:func:`PyTupleWriter_Create`.
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_Create`.
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:
Expand Down
14 changes: 14 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,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 <pytuplewriter>` to create :class:`tuple`:

* :c:func:`PyTupleWriter_Create`
* :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
----------------------
Expand Down Expand Up @@ -915,6 +926,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 <pytuplewriter>` instead.
(Contributed by Victor Stinner in :gh:`139888`.)

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

Expand Down
20 changes: 19 additions & 1 deletion Include/cpython/tupleobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down Expand Up @@ -42,3 +42,21 @@ 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 PyTupleWriter;

PyAPI_FUNC(PyTupleWriter*) PyTupleWriter_Create(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);
2 changes: 2 additions & 0 deletions Include/internal/pycore_freelist_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ extern "C" {
# define Py_object_stack_chunks_MAXFREELIST 4
# define Py_unicode_writers_MAXFREELIST 1
# define Py_bytes_writers_MAXFREELIST 1
# define Py_tuple_writers_MAXFREELIST 1
# define Py_pycfunctionobject_MAXFREELIST 16
# define Py_pycmethodobject_MAXFREELIST 16
# define Py_pymethodobjects_MAXFREELIST 20
Expand Down Expand Up @@ -63,6 +64,7 @@ struct _Py_freelists {
struct _Py_freelist object_stack_chunks;
struct _Py_freelist unicode_writers;
struct _Py_freelist bytes_writers;
struct _Py_freelist tuple_writers;
struct _Py_freelist pycfunctionobject;
struct _Py_freelist pycmethodobject;
struct _Py_freelist pymethodobjects;
Expand Down
66 changes: 66 additions & 0 deletions Lib/test/test_capi/test_tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_Create()
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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Add a :ref:`PyTupleWriter API <pytuplewriter>` to create :class:`tuple`:

* :c:func:`PyTupleWriter_Create`
* :c:func:`PyTupleWriter_Add`
* :c:func:`PyTupleWriter_AddSteal`
* :c:func:`PyTupleWriter_AddArray`
* :c:func:`PyTupleWriter_Finish`
* :c:func:`PyTupleWriter_Discard`

Patch by Victor Stinner.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Deprecate the :c:func:`_PyTuple_Resize` function: use the :ref:`PyTupleWriter
API <pytuplewriter>` instead.
Loading
Loading