Skip to content

gh-158660: Add PyListWriter C API - #160127

Open
vstinner wants to merge 11 commits into
python:mainfrom
vstinner:list_writer
Open

vstinner wants to merge 11 commits into
python:mainfrom
vstinner:list_writer

Conversation

@vstinner

@vstinner vstinner commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

@vstinner

Copy link
Copy Markdown
Member Author

If a list writer is used by multiple threads, listwriter_resize() can call _PyObject_GC_SET_SHARED() on Free Threading.

@vstinner

Copy link
Copy Markdown
Member Author

This C API is just an abstraction on top of existing PyList functions. PyListWriter_Create() doesn't allocate a PyListWriter structure. In fact, it's just a PyListObject* pointer! It is the same design than the PyLongWriter API.

@read-the-docs-community

read-the-docs-community Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

@vstinner

Copy link
Copy Markdown
Member Author

Another advantage of using this C API instead of PyList_New() is that the Python list object is only tracked by the GC when PyListWriter_Finish() is called. You cannot see the uninitialized list using the gc module.

@vstinner

Copy link
Copy Markdown
Member Author

Tests / Hypothesis tests on Ubuntu (pull_request): Failing after 10m

Oh, this failure seems to be unrelated to my change:

FAIL: test_system_site_packages (test.test_site.ImportSideEffectTests.test_system_site_packages)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/runner/work/cpython/cpython-ro-srcdir/Lib/test/test_site.py", line 645, in test_system_site_packages
    self.assertIn(system_paths['purelib'], sys_path)
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: '/home/runner/work/cpython/cpython-builddir/hypovenv/lib/python3.16/site-packages' not found in ['', '/usr/local/lib/python316.zip', '/home/runner/work/cpython/cpython-ro-srcdir/Lib', '/home/runner/work/cpython/cpython-builddir/build/lib.linux-x86_64-3.16', '/tmp/test_python_0lhq3tgr/test.test_site.ImportSideEffectTests.test_system_site_packages-venv-d_ynd3hp/lib/python3.16/site-packages']

@vstinner

vstinner commented Oct 22, 2026 •

Copy link
Copy Markdown
Member Author

I ran a benchmark to compare PyListWriter_Append() to PyList_SetItem() function and to PyList_SET_ITEM() macro. On Free Threading, PyListWriter_Append() is between 2.9x and 3.9x faster than PyList_SetItem() function, and between 1.6x and 1.7x slower than the PyList_SET_ITEM() macro (which is excluded from the limited C API).

PyListWriter_Append() is the function with the highest overhead. PyListWriter_Extend() and PyListWriter_ExtendArray() should have a way lower overhead per item, since they add multiple items at once.

I ran the benchmark on Free Threaded build on Linux (gcc -O3). Benchmark script:

import pyperf, functools, _testcapi
runner = pyperf.Runner()
sizes = (0, 10, 100, 1_000)

for list_len in sizes:
    test_list = list(range(list_len))
    func = functools.partial(_testcapi.bench_setitem_macro, test_list)
    runner.bench_time_func(f'setitem macro {list_len:,}-list', func)

for list_len in sizes:
    test_list = list(range(list_len))
    func = functools.partial(_testcapi.bench_setitem_func, test_list)
    runner.bench_time_func(f'setitem func {list_len:,}-list', func)

for list_len in sizes:
    test_list = list(range(list_len))
    func = functools.partial(_testcapi.bench_writer, test_list)
    runner.bench_time_func(f'writer {list_len:,}-list', func)

Results:

setitem macro 0-list: Mean +- std dev: 12.1 ns +- 0.2 ns
setitem macro 10-list: Mean +- std dev: 33.4 ns +- 1.3 ns
setitem macro 100-list: Mean +- std dev: 219 ns +- 3 ns
setitem macro 1,000-list: Mean +- std dev: 2.21 us +- 0.02 us

setitem func 0-list: Mean +- std dev: 12.3 ns +- 0.1 ns
setitem func 10-list: Mean +- std dev: 155 ns +- 1 ns
setitem func 100-list: Mean +- std dev: 1.47 us +- 0.01 us
setitem func 1,000-list: Mean +- std dev: 14.4 us +- 0.1 us

writer 0-list: Mean +- std dev: 15.0 ns +- 0.1 ns
writer 10-list: Mean +- std dev: 52.8 ns +- 0.2 ns
writer 100-list: Mean +- std dev: 378 ns +- 18 ns
writer 1,000-list: Mean +- std dev: 3.70 us +- 0.04 us
  • "setitem macro" is the fastest way to create a list: PyList_New() + PyList_SET_ITEM() macro -- NOT AVAILABLE in abit3t
  • "setitem func" is the abi3t way, function calls: PyList_New() + PyList_SetItem() function
  • "writer" is this PR: PyListWriter_Create() + PyListWriter_Append() + PyListWriter_Finish()

PyList_SetItem() is between 4.6x and 6.7x slower than the PyList_SET_ITEM() macro: well, that's the issue #158660, PyList_SetItem() function is way slower than PyList_SET_ITEM() macro on Free Threading.

PyListWriter_Append() is between 1.6x and 1.7x slower than the PyList_SET_ITEM() macro, and it's between 2.9x and 3.9x faster than PyList_SetItem() function.

On Free Threading, PyListWriter_Append() is way faster than PyList_SetItem() function because it avoids locking.

Patch adding 3 bench functions to _testcapi:

Details
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 033ee3234cf..a3a8d9989c0 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2889,6 +2889,112 @@ uptime_bsd(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
 #endif
 
 
+static PyObject*
+bench_setitem_macro(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t loops;
+    if (!PyArg_ParseTuple(args, "On", &list, &loops)) {
+        return NULL;
+    }
+
+    Py_ssize_t len = PyList_GET_SIZE(list);
+
+    PyTime_t t1, t2;
+    (void)PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyObject *result = PyList_New(len);
+        if (result == NULL) {
+            return NULL;
+        }
+
+        for (Py_ssize_t j=0; j < len; j++) {
+            PyObject *item = Py_NewRef(PyList_GET_ITEM(list, j));
+            PyList_SET_ITEM(result, j, item);
+        }
+
+        Py_DECREF(result);
+    }
+    (void)PyTime_PerfCounterRaw(&t2);
+
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+
+static PyObject*
+bench_setitem_func(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t loops;
+    if (!PyArg_ParseTuple(args, "On", &list, &loops)) {
+        return NULL;
+    }
+
+    Py_ssize_t len = PyList_GET_SIZE(list);
+
+    PyTime_t t1, t2;
+    (void)PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyObject *result = PyList_New(len);
+        if (result == NULL) {
+            return NULL;
+        }
+
+        for (Py_ssize_t j=0; j < len; j++) {
+            PyObject *item = Py_NewRef(PyList_GET_ITEM(list, j));
+            if (PyList_SetItem(result, j, item) < 0) {
+                Py_DECREF(result);
+                return NULL;
+            }
+        }
+
+        Py_DECREF(result);
+    }
+    (void)PyTime_PerfCounterRaw(&t2);
+
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+
+static PyObject*
+bench_writer(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t loops;
+    if (!PyArg_ParseTuple(args, "On", &list, &loops)) {
+        return NULL;
+    }
+
+    Py_ssize_t len = PyList_GET_SIZE(list);
+
+    PyTime_t t1, t2;
+    (void)PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyListWriter *writer = PyListWriter_Create(len);
+        if (writer == NULL) {
+            return NULL;
+        }
+
+        for (Py_ssize_t j=0; j < len; j++) {
+            PyObject *item = PyList_GET_ITEM(list, j);
+            if (PyListWriter_Append(writer, item) < 0) {
+                PyListWriter_Discard(writer);
+                return NULL;
+            }
+        }
+
+        PyObject *result = PyListWriter_Finish(writer);
+        if (result == NULL) {
+            return NULL;
+        }
+        Py_DECREF(result);
+    }
+    (void)PyTime_PerfCounterRaw(&t2);
+
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+
 static PyMethodDef TestMethods[] = {
     {"set_errno",               set_errno,                       METH_VARARGS},
     {"test_config",             test_config,                     METH_NOARGS},
@@ -2988,6 +3094,9 @@ static PyMethodDef TestMethods[] = {
 #ifdef HAVE_SYSCTLBYNAME
     {"uptime_bsd", uptime_bsd, METH_NOARGS},
 #endif
+    {"bench_setitem_macro", bench_setitem_macro, METH_VARARGS},
+    {"bench_setitem_func", bench_setitem_func, METH_VARARGS},
+    {"bench_writer", bench_writer, METH_VARARGS},
     {NULL, NULL} /* sentinel */
 };
 

@vstinner

Copy link
Copy Markdown
Member Author

I also ran benchmarks on PyListWriter_Extend() and PyListWriter_ExtendArray() functions.

setitem macro 0-list: Mean +- std dev: 15.4 ns +- 0.2 ns
setitem macro 10-list: Mean +- std dev: 43.5 ns +- 2.2 ns
setitem macro 100-list: Mean +- std dev: 243 ns +- 4 ns
setitem macro 1,000-list: Mean +- std dev: 2.86 us +- 0.15 us

writer append 0-list: Mean +- std dev: 15.9 ns +- 1.2 ns
writer append 10-list: Mean +- std dev: 53.0 ns +- 0.7 ns
writer append 100-list: Mean +- std dev: 394 ns +- 18 ns
writer append 1,000-list: Mean +- std dev: 3.52 us +- 0.26 us

writer extend 0-list: Mean +- std dev: 29.5 ns +- 1.1 ns
writer extend 10-list: Mean +- std dev: 55.1 ns +- 0.8 ns
writer extend 100-list: Mean +- std dev: 174 ns +- 18 ns
writer extend 1,000-list: Mean +- std dev: 1.50 us +- 0.15 us

writer extend array 0-list: Mean +- std dev: 21.0 ns +- 1.0 ns
writer extend array 10-list: Mean +- std dev: 34.0 ns +- 0.7 ns
writer extend array 100-list: Mean +- std dev: 165 ns +- 11 ns
writer extend array 1,000-list: Mean +- std dev: 1.43 us +- 0.10 us

PyListWriter_Extend() is between 1.3x slower and... 1.9x faster than PyList_SET_ITEM() macro! It's between 1.04x and 2.3x faster than PyListWriter_Append().

PyListWriter_ExtendArray() is between 1.3x and 2.0x faster than PyList_SET_ITEM() macro! It's between 1.6x and 2.6x faster than PyListWriter_Append().

Well, it's not surprising that PyListWriter_Extend() and PyListWriter_ExtendArray() are faster than the PyList_SET_ITEM() macro, since the process many items in a single function call.

Benchmark script:

Details
import pyperf, functools, _testcapi
runner = pyperf.Runner()
sizes = (0, 10, 100, 1_000)

for list_len in sizes:
    test_list = list(range(list_len))
    func = functools.partial(_testcapi.bench_setitem_macro, test_list)
    runner.bench_time_func(f'setitem macro {list_len:,}-list', func)

for list_len in sizes:
    test_list = list(range(list_len))
    func = functools.partial(_testcapi.bench_writer_append, test_list)
    runner.bench_time_func(f'writer append {list_len:,}-list', func)

for list_len in sizes:
    test_list = list(range(list_len))
    func = functools.partial(_testcapi.bench_writer_extend, test_list)
    runner.bench_time_func(f'writer extend {list_len:,}-list', func)

for list_len in sizes:
    test_list = list(range(list_len))
    func = functools.partial(_testcapi.bench_writer_extend_array, test_list)
    runner.bench_time_func(f'writer extend array {list_len:,}-list', func)

Patch adding C benchmarks:

Details
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 033ee3234cf..c191d12c862 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2889,6 +2889,185 @@ uptime_bsd(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
 #endif
 
 
+static PyObject*
+bench_setitem_macro(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t loops;
+    if (!PyArg_ParseTuple(args, "On", &list, &loops)) {
+        return NULL;
+    }
+
+    Py_ssize_t len = PyList_GET_SIZE(list);
+
+    PyTime_t t1, t2;
+    (void)PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyObject *result = PyList_New(len);
+        if (result == NULL) {
+            return NULL;
+        }
+
+        for (Py_ssize_t j=0; j < len; j++) {
+            PyObject *item = Py_NewRef(PyList_GET_ITEM(list, j));
+            PyList_SET_ITEM(result, j, item);
+        }
+
+        Py_DECREF(result);
+    }
+    (void)PyTime_PerfCounterRaw(&t2);
+
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+
+static PyObject*
+bench_setitem_func(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t loops;
+    if (!PyArg_ParseTuple(args, "On", &list, &loops)) {
+        return NULL;
+    }
+
+    Py_ssize_t len = PyList_GET_SIZE(list);
+
+    PyTime_t t1, t2;
+    (void)PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyObject *result = PyList_New(len);
+        if (result == NULL) {
+            return NULL;
+        }
+
+        for (Py_ssize_t j=0; j < len; j++) {
+            PyObject *item = Py_NewRef(PyList_GET_ITEM(list, j));
+            if (PyList_SetItem(result, j, item) < 0) {
+                Py_DECREF(result);
+                return NULL;
+            }
+        }
+
+        Py_DECREF(result);
+    }
+    (void)PyTime_PerfCounterRaw(&t2);
+
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+
+static PyObject*
+bench_writer_append(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t loops;
+    if (!PyArg_ParseTuple(args, "On", &list, &loops)) {
+        return NULL;
+    }
+
+    Py_ssize_t len = PyList_GET_SIZE(list);
+
+    PyTime_t t1, t2;
+    (void)PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyListWriter *writer = PyListWriter_Create(len);
+        if (writer == NULL) {
+            return NULL;
+        }
+
+        for (Py_ssize_t j=0; j < len; j++) {
+            PyObject *item = PyList_GET_ITEM(list, j);
+            if (PyListWriter_Append(writer, item) < 0) {
+                PyListWriter_Discard(writer);
+                return NULL;
+            }
+        }
+
+        PyObject *result = PyListWriter_Finish(writer);
+        if (result == NULL) {
+            return NULL;
+        }
+        Py_DECREF(result);
+    }
+    (void)PyTime_PerfCounterRaw(&t2);
+
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+
+static PyObject*
+bench_writer_extend(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t loops;
+    if (!PyArg_ParseTuple(args, "On", &list, &loops)) {
+        return NULL;
+    }
+
+    Py_ssize_t len = PyList_GET_SIZE(list);
+
+    PyTime_t t1, t2;
+    (void)PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyListWriter *writer = PyListWriter_Create(len);
+        if (writer == NULL) {
+            return NULL;
+        }
+
+        if (PyListWriter_Extend(writer, list) < 0) {
+            PyListWriter_Discard(writer);
+            return NULL;
+        }
+
+        PyObject *result = PyListWriter_Finish(writer);
+        if (result == NULL) {
+            return NULL;
+        }
+        Py_DECREF(result);
+    }
+    (void)PyTime_PerfCounterRaw(&t2);
+
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+
+static PyObject*
+bench_writer_extend_array(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *list;
+    Py_ssize_t loops;
+    if (!PyArg_ParseTuple(args, "On", &list, &loops)) {
+        return NULL;
+    }
+
+    PyObject **list_items = _PyList_CAST(list)->ob_item;
+    Py_ssize_t len = PyList_GET_SIZE(list);
+
+    PyTime_t t1, t2;
+    (void)PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyListWriter *writer = PyListWriter_Create(len);
+        if (writer == NULL) {
+            return NULL;
+        }
+
+        if (PyListWriter_ExtendArray(writer, list_items, len) < 0) {
+            PyListWriter_Discard(writer);
+            return NULL;
+        }
+
+        PyObject *result = PyListWriter_Finish(writer);
+        if (result == NULL) {
+            return NULL;
+        }
+        Py_DECREF(result);
+    }
+    (void)PyTime_PerfCounterRaw(&t2);
+
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+
 static PyMethodDef TestMethods[] = {
     {"set_errno",               set_errno,                       METH_VARARGS},
     {"test_config",             test_config,                     METH_NOARGS},
@@ -2988,6 +3167,11 @@ static PyMethodDef TestMethods[] = {
 #ifdef HAVE_SYSCTLBYNAME
     {"uptime_bsd", uptime_bsd, METH_NOARGS},
 #endif
+    {"bench_setitem_macro", bench_setitem_macro, METH_VARARGS},
+    {"bench_setitem_func", bench_setitem_func, METH_VARARGS},
+    {"bench_writer_append", bench_writer_append, METH_VARARGS},
+    {"bench_writer_extend", bench_writer_extend, METH_VARARGS},
+    {"bench_writer_extend_array", bench_writer_extend_array, METH_VARARGS},
     {NULL, NULL} /* sentinel */
 };
 

Comment thread Doc/c-api/list.rst
Extend the list writer with the contents of *items*.

On success, return ``0``.
On error, set an exception and return ``-1``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should say what happens to the refcounts of items on error (also on success: does it steal references?).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It behaves as PyTuple_FromArray(): new references are created. I will reuse PyTuple_FromArray() phrasing: "copy references from array to the new tuple".

Comment thread Doc/c-api/list.rst Outdated
@pitrou

pitrou commented Oct 22, 2026

Copy link
Copy Markdown
Member

+1 on the principle, this looks like a very useful addition.

@ngoldbaum

ngoldbaum commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

I implemented wrappers in PyO3 for Victor's API and ran a new round of benchmarks with an implementation using PyListWriter_AppendSteal. It is an improvement over the iterator approach but I'm not able to approach the performance of PyList_SET_ITEM because of per-element overhead.

I have two ideas to improve Victor's initial proposal.

  • We can help performance for small lists by avoiding zero-initialization of the initial allocation. The list stays untracked while building, and its length counts the initialized items, so cleanup does not read the uninitialized capacity.
  • We can improve performance for large lists in the free-threaded and limited-API builds by adding an ExtendArraySteal function. ExtendArray expects borrowed references and must incref, so that's why I want a stealing API. AppendSteal has per-item overhead which ExtendArraySteal would amortize.

Here is a patch that removes the zero-initialization from this PR:

Details
diff --git a/Objects/listobject.c b/Objects/listobject.c
--- a/Objects/listobject.c
+++ b/Objects/listobject.c
@@ -4415,12 +4415,29 @@
         return NULL;
     }
 
-    PyObject *list = PyList_New(size);
+    PyObject *list = PyList_New(0);
     if (list == NULL) {
         return NULL;
     }
-    Py_SET_SIZE(list, 0);
     _PyObject_GC_UNTRACK(list);
+
+    // Keep the length at zero until items have been initialized.
+    if (size > 0) {
+#ifdef Py_GIL_DISABLED
+        _PyListArray *array = list_allocate_array(size);
+        PyObject **items = array != NULL ? array->ob_item : NULL;
+#else
+        PyObject **items = PyMem_New(PyObject *, size);
+#endif
+        if (items == NULL) {
+            Py_DECREF(list);
+            PyErr_NoMemory();
+            return NULL;
+        }
+        PyListObject *list_obj = _PyList_CAST(list);
+        list_obj->ob_item = items;
+        list_obj->allocated = size;
+    }
 
     PyListWriter *writer = (PyListWriter*)list;
     assert(listwriter_check_consistency(writer));

Here is a plot that illustrates the timings for all three approaches, along with the iterator approach, PyList_SetItem, and PyList_SET_ITEM on a version-specific build:

image

Note that this is a somewhat different workload than Victor's test above. PyO3 streams input items with a size hint, so a batched approach is most natural. Victor passes an existing array directly, and his range(1000) inputs are entirely cached, immortal integers. Using integers inside the range where they are immortal impacts the timing, because the INCREF to take ownership is free.

I lean towards leaving in AppendSteal with the zero-initialization hack above since it seems to be either faster or a wash on all builds compared with PyList_SetItem and there's no need for making for generating different code on abi3 and abi3t, assuming the APIs make their way into the stable ABI.

In release mode, no longer initialize items to NULL for better
performance. The caller is responsible to initialize all items before
calling PyListWriter_Finish().
@vstinner

Copy link
Copy Markdown
Member Author

I have two ideas to improve Victor's initial proposal.
We can help performance for small lists by avoiding zero-initialization of the initial allocation. (...)

Oh sure, there is no need to initialize items to NULL since the list is not tracked by the GC. I implemented this optimization.

We can improve performance for large lists in the free-threaded and limited-API builds by adding an ExtendArraySteal function.

I'm not sure how other C API Working Group members feel about functions stealing references. But ok, I added the function!

@vstinner
vstinner marked this pull request as ready for review October 22, 2026 22:04
@vstinner

Copy link
Copy Markdown
Member Author

I mark the PR as ready for review.

@vstinner

Copy link
Copy Markdown
Member Author

@encukou: So what do you think of this APi?

@encukou

encukou commented Oct 23, 2026

Copy link
Copy Markdown
Member

PyListWriter_Append is unnecesary: you can call PyListWriter_ExtendArray with a size of 1.

the Python list object is only tracked by the GC when PyListWriter_Finish() is called. You cannot see the uninitialized list using the gc module.

How important is this?

  • If this is a valid list, we don't really need new writer type AFAICS -- we could use PyList_NewEmpty(Py_ssize_t n_preallocated) and PyListWriter_ExtendArray[Steal].
  • If this is not a valid list object, I'd prefer if ob_type was also filled in at PyListWriter_Finish time.

I'm not sure how other C API Working Group members feel about functions stealing references.

If there's a function that doesn't steal, it's fine to add an additional one with a Steal suffix appended.

Personally I'd prefer separating suffixes like this with an underscore (PyListWriter_Append_Steal).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants