Skip to content

Commit 8404760

Browse files
committed
Add PyListWriter_FromFormat()
1 parent 9efaf87 commit 8404760

10 files changed

Lines changed: 192 additions & 41 deletions

File tree

‎Doc/c-api/list.rst‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,12 @@ On Free Threading, it's more efficient than the ``PyList`` C API to create a
320320
instead of copying references.
321321
322322
On error, all *items* references are also cleared.
323+
324+
.. c:function:: int PyListWriter_FromFormat(PyListWriter *writer, const char *format, ...)
325+
326+
Add values using :ref:`building values format <arg-parsing>`.
327+
328+
Example adding 3 integers: ``PyListWriter_FromFormat(writer, "iii", 1, 2, 3)``.
329+
330+
On success, return ``0``.
331+
On error, set an exception, leave the writer unchanged, and return ``-1``.

‎Doc/whatsnew/3.16.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,7 @@ New features
13231323
* :c:func:`PyListWriter_Extend`
13241324
* :c:func:`PyListWriter_ExtendArray`
13251325
* :c:func:`PyListWriter_ExtendArraySteal`
1326+
* :c:func:`PyListWriter_FromFormat`
13261327

13271328
(Contributed by Victor Stinner in :gh:`158660`.)
13281329

‎Include/cpython/listobject.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,7 @@ PyAPI_FUNC(int) PyListWriter_ExtendArraySteal(
7777
PyListWriter *writer,
7878
PyObject *const *items,
7979
Py_ssize_t nitem);
80+
PyAPI_FUNC(int) PyListWriter_FromFormat(
81+
PyListWriter *writer,
82+
const char *format,
83+
...);

‎Include/internal/pycore_modsupport.h‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ PyAPI_FUNC(int) _PyArg_CheckPositional(const char *, Py_ssize_t,
3232
(((min) <= (nargs) && (nargs) <= (max)) \
3333
|| _PyArg_CheckPositional((funcname), (nargs), (min), (max)))
3434

35-
extern PyObject ** _Py_VaBuildStack(
35+
extern int _Py_VaBuildArray(
36+
PyObject ***array,
37+
Py_ssize_t *size,
3638
PyObject **small_stack,
3739
Py_ssize_t small_stack_len,
3840
const char *format,
39-
va_list va,
40-
Py_ssize_t *p_nargs);
41+
va_list va);
4142

4243
extern PyObject* _PyModule_CreateInitialized(PyModuleDef*, int apiver);
4344

‎Lib/test/test_capi/test_list.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,29 @@ def test_extend_array_steal(self):
460460
# Test PyListWriter_ExtendArraySteal()
461461
self.check_extend('extend_array_steal')
462462

463+
def test_build_value(self):
464+
# Test PyListWriter_FromFormat()
465+
466+
# Allocate a small array on the stack (1 item)
467+
writer = self.create_writer(0)
468+
writer.from_format_i(123)
469+
self.list_equal(writer.finish(), [123])
470+
471+
# Allocate an array on the heap (16 items)
472+
items = tuple(range(100, 100+16))
473+
writer = self.create_writer(0)
474+
writer.from_format_i16(items)
475+
self.list_equal(writer.finish(), items)
476+
477+
# Test error: call PyListWriter_FromFormat(writer, "iiO", 1, 2, NULL).
478+
# First items before the error must not be added.
479+
writer = self.create_writer(0)
480+
writer.append("before")
481+
with self.assertRaises(SystemError):
482+
writer.from_format_error()
483+
writer.append("after")
484+
self.list_equal(writer.finish(), ["before", "after"])
485+
463486
def test_empty(self):
464487
list_get_allocated = _testcapi._list_get_allocated
465488
empty_tuple = ()

‎Misc/NEWS.d/next/C_API/2026-10-21-16-35-42.gh-issue-158660.KvixNp.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ created list. Add functions:
1111
* :c:func:`PyListWriter_Extend`
1212
* :c:func:`PyListWriter_ExtendArray`
1313
* :c:func:`PyListWriter_ExtendArraySteal`
14+
* :c:func:`PyListWriter_FromFormat`
1415

1516
Patch by Victor Stinner.

‎Modules/_testcapi/list.c‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,83 @@ writer_extend_array_steal(PyObject *self_raw, PyObject *tuple)
288288
}
289289

290290

291+
// Test PyListWriter_FromFormat("i")
292+
static PyObject*
293+
writer_from_format_i(PyObject *self_raw, PyObject *arg)
294+
{
295+
WriterObject *self = (WriterObject *)self_raw;
296+
if (writer_check(self) < 0) {
297+
return NULL;
298+
}
299+
300+
int value;
301+
if (!PyArg_Parse(arg, "i", &value)) {
302+
return NULL;
303+
}
304+
305+
if (PyListWriter_FromFormat(self->writer, "i", value) < 0) {
306+
return NULL;
307+
}
308+
Py_RETURN_NONE;
309+
}
310+
311+
312+
// Test PyListWriter_FromFormat("i")
313+
static PyObject*
314+
writer_from_format_i16(PyObject *self_raw, PyObject *tuple)
315+
{
316+
WriterObject *self = (WriterObject *)self_raw;
317+
if (writer_check(self) < 0) {
318+
return NULL;
319+
}
320+
321+
// Test size larger than small_array size in PyListWriter_FromFormat()
322+
int values[16];
323+
Py_ssize_t len = Py_ARRAY_LENGTH(values);
324+
if (!PyTuple_Check(tuple) || PyTuple_GET_SIZE(tuple) != len) {
325+
PyErr_Format(PyExc_ValueError,
326+
"expect a tuple of %zd integers", len);
327+
return NULL;
328+
}
329+
for (Py_ssize_t i = 0; i < len; i++) {
330+
int value = PyLong_AsInt(PyTuple_GET_ITEM(tuple, i));
331+
if (value == -1 && PyErr_Occurred()) {
332+
return NULL;
333+
}
334+
values[i] = value;
335+
}
336+
337+
int res = PyListWriter_FromFormat(self->writer,
338+
"iiiiiiiiiiiiiiii",
339+
values[0], values[1], values[2], values[3],
340+
values[4], values[5], values[6], values[7],
341+
values[8], values[9], values[10], values[11],
342+
values[12], values[13], values[14], values[15]
343+
);
344+
if (res < 0) {
345+
return NULL;
346+
}
347+
Py_RETURN_NONE;
348+
}
349+
350+
351+
// Test PyListWriter_FromFormat() error
352+
static PyObject*
353+
writer_from_format_error(PyObject *self_raw, PyObject *Py_UNUSED(args))
354+
{
355+
WriterObject *self = (WriterObject *)self_raw;
356+
if (writer_check(self) < 0) {
357+
return NULL;
358+
}
359+
360+
int res = PyListWriter_FromFormat(self->writer, "iiO", 1, 2, NULL);
361+
if (res < 0) {
362+
return NULL;
363+
}
364+
Py_RETURN_NONE;
365+
}
366+
367+
291368
// Test PyListWriter_Finish()
292369
static PyObject*
293370
writer_finish(PyObject *self_raw, PyObject *Py_UNUSED(args))
@@ -324,6 +401,9 @@ static PyMethodDef writer_methods[] = {
324401
{"extend", _PyCFunction_CAST(writer_extend), METH_O},
325402
{"extend_array", _PyCFunction_CAST(writer_extend_array), METH_O},
326403
{"extend_array_steal", _PyCFunction_CAST(writer_extend_array_steal), METH_O},
404+
{"from_format_i", _PyCFunction_CAST(writer_from_format_i), METH_O},
405+
{"from_format_i16", _PyCFunction_CAST(writer_from_format_i16), METH_O},
406+
{"from_format_error", _PyCFunction_CAST(writer_from_format_error), METH_NOARGS},
327407
{"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS},
328408
{"discard", _PyCFunction_CAST(writer_discard), METH_NOARGS},
329409
{NULL, NULL} /* sentinel */

‎Objects/call.c‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -517,12 +517,6 @@ static PyObject *
517517
_PyObject_CallFunctionVa(PyThreadState *tstate, PyObject *callable,
518518
const char *format, va_list va)
519519
{
520-
PyObject* small_stack[_PY_FASTCALL_SMALL_STACK];
521-
const Py_ssize_t small_stack_len = Py_ARRAY_LENGTH(small_stack);
522-
PyObject **stack;
523-
Py_ssize_t nargs, i;
524-
PyObject *result;
525-
526520
if (callable == NULL) {
527521
return null_error(tstate);
528522
}
@@ -531,12 +525,17 @@ _PyObject_CallFunctionVa(PyThreadState *tstate, PyObject *callable,
531525
return _PyObject_CallNoArgsTstate(tstate, callable);
532526
}
533527

534-
stack = _Py_VaBuildStack(small_stack, small_stack_len,
535-
format, va, &nargs);
536-
if (stack == NULL) {
528+
PyObject* small_stack[_PY_FASTCALL_SMALL_STACK];
529+
PyObject **stack;
530+
Py_ssize_t nargs;
531+
if (_Py_VaBuildArray(&stack, &nargs,
532+
small_stack, Py_ARRAY_LENGTH(small_stack),
533+
format, va) < 0) {
537534
return NULL;
538535
}
539536
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, callable);
537+
538+
PyObject *result;
540539
if (nargs == 1 && PyTuple_Check(stack[0])) {
541540
/* Special cases for backward compatibility:
542541
- PyObject_CallFunction(func, "O", tuple) calls func(*tuple)
@@ -553,7 +552,7 @@ _PyObject_CallFunctionVa(PyThreadState *tstate, PyObject *callable,
553552
stack, nargs, NULL);
554553
}
555554

556-
for (i = 0; i < nargs; ++i) {
555+
for (Py_ssize_t i = 0; i < nargs; ++i) {
557556
Py_DECREF(stack[i]);
558557
}
559558
if (stack != small_stack) {
@@ -640,15 +639,19 @@ callmethod(PyThreadState *tstate, PyObject *obj, PyObject *name,
640639

641640
/* Build the positional arguments from the format string. */
642641
PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];
643-
Py_ssize_t nargs = 0;
644-
PyObject **built = NULL;
642+
PyObject **built;
643+
Py_ssize_t nargs;
645644
if (format != NULL && *format != '\0') {
646-
built = _Py_VaBuildStack(small_stack, _PY_FASTCALL_SMALL_STACK,
647-
format, va, &nargs);
648-
if (built == NULL) {
645+
if (_Py_VaBuildArray(&built, &nargs,
646+
small_stack, _PY_FASTCALL_SMALL_STACK,
647+
format, va) < 0) {
649648
goto exit;
650649
}
651650
}
651+
else {
652+
built = NULL;
653+
nargs = 0;
654+
}
652655

653656
/* Backward compat: a single tuple from "O" is unpacked. */
654657
PyObject *const *args = built;

‎Objects/listobject.c‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4674,3 +4674,29 @@ PyListWriter_ExtendArraySteal(PyListWriter *writer,
46744674
{
46754675
return listwriter_extend_array(writer, items, nitem, 1);
46764676
}
4677+
4678+
4679+
int
4680+
PyListWriter_FromFormat(PyListWriter *writer, const char *format, ...)
4681+
{
4682+
PyObject* small_array[8];
4683+
Py_ssize_t size;
4684+
PyObject **array;
4685+
4686+
va_list vargs;
4687+
va_start(vargs, format);
4688+
int res = _Py_VaBuildArray(
4689+
&array, &size,
4690+
small_array, Py_ARRAY_LENGTH(small_array),
4691+
format, vargs);
4692+
va_end(vargs);
4693+
if (res < 0) {
4694+
return -1;
4695+
}
4696+
4697+
res = PyListWriter_ExtendArraySteal(writer, array, size);
4698+
if (array != small_array) {
4699+
PyMem_Free(array);
4700+
}
4701+
return res;
4702+
}

‎Python/modsupport.c‎

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -549,52 +549,55 @@ va_build_value(const char *format, va_list va)
549549
return retval;
550550
}
551551

552-
PyObject **
553-
_Py_VaBuildStack(PyObject **small_stack, Py_ssize_t small_stack_len,
554-
const char *format, va_list va, Py_ssize_t *p_nargs)
552+
int
553+
_Py_VaBuildArray(PyObject ***array, Py_ssize_t *array_size,
554+
PyObject **small_array, Py_ssize_t small_array_len,
555+
const char *format, va_list va)
555556
{
556-
const char *f;
557-
Py_ssize_t n;
558-
va_list lva;
559-
PyObject **stack;
560-
int res;
561-
562-
n = countformat(format, '\0');
557+
Py_ssize_t n = countformat(format, '\0');
563558
if (n < 0) {
564-
*p_nargs = 0;
565-
return NULL;
559+
goto error;
566560
}
567561

568562
if (n == 0) {
569-
*p_nargs = 0;
570-
return small_stack;
563+
*array = small_array;
564+
*array_size = 0;
565+
return 0;
571566
}
572567

573-
if (n <= small_stack_len) {
574-
stack = small_stack;
568+
PyObject **stack;
569+
if (n <= small_array_len) {
570+
stack = small_array;
575571
}
576572
else {
577573
stack = PyMem_Malloc(n * sizeof(stack[0]));
578574
if (stack == NULL) {
579575
PyErr_NoMemory();
580-
return NULL;
576+
goto error;
581577
}
582578
}
583579

580+
va_list lva;
584581
va_copy(lva, va);
585-
f = format;
586-
res = do_mkstack(stack, &f, &lva, '\0', n);
582+
const char *f = format;
583+
int res = do_mkstack(stack, &f, &lva, '\0', n);
587584
va_end(lva);
588585

589586
if (res < 0) {
590-
if (stack != small_stack) {
587+
if (stack != small_array) {
591588
PyMem_Free(stack);
592589
}
593-
return NULL;
590+
goto error;
594591
}
595592

596-
*p_nargs = n;
597-
return stack;
593+
*array = stack;
594+
*array_size = n;
595+
return 0;
596+
597+
error:
598+
*array = NULL;
599+
*array_size = -1;
600+
return -1;
598601
}
599602

600603

0 commit comments

Comments
 (0)