Skip to main content

Include/internal/pycore_list.h

Include/internal/pycore_list.h is the private header that exposes list internals to the interpreter and the compiler. It defines the list iterator struct, an inline memory-repeat helper used by list.copy, the distinction between allocated capacity and logical size, a fast item-access macro, and the free-list size limit.

Source:

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_list.h

Map

LinesSymbolRole
~10_PyListIterObjectInternal layout of a list iterator
~20_Py_memory_repeatInline helper for list.copy bulk fill
~30PyListObject->ob_allocAllocated slot count (capacity) vs ob_size (logical length)
~40_PyList_ITEMSMacro for direct pointer to ob_item array
~50PyList_MAXFREELISTMaximum number of lists held on the free list

Reading

_PyListIterObject, iterator internal layout

The list iterator keeps three fields: a strong reference to the list, the current index, and a length snapshot taken at construction time. The length snapshot is used to detect structural changes to the list mid-iteration.

// CPython: Include/internal/pycore_list.h:10 _PyListIterObject
typedef struct {
PyObject_HEAD
Py_ssize_t it_index;
PyListObject *it_seq; /* Set to NULL when iterator is exhausted */
} _PyListIterObject;

it_seq is set to NULL and the reference dropped when the iterator is exhausted or when the list is deleted while the iterator is live. Callers must null-check before accessing the field.

_Py_memory_repeat and list.copy

list.copy uses a bulk-copy path rather than a per-element loop. The helper _Py_memory_repeat takes a base pointer, a unit size, and a repeat count, then fills the destination by doubling the already-written prefix on each pass. This gives O(log n) memcpy calls for large lists instead of O(n).

// CPython: Include/internal/pycore_list.h:20 _Py_memory_repeat
static inline void
_Py_memory_repeat(char *dest, Py_ssize_t len_unit, Py_ssize_t count)
{
Py_ssize_t done = len_unit;
while (done < count * len_unit) {
Py_ssize_t n = Py_MIN(done, count * len_unit - done);
memcpy(dest + done, dest, n);
done += n;
}
}

The caller (list_copy_impl in Objects/listobject.c) fills slot 0 first, then calls this helper to replicate it across the remaining slots, incrementing reference counts separately. Only the reference-count loop is O(n).

ob_alloc vs ob_size, _PyList_ITEMS, and PyList_MAXFREELIST

PyListObject carries two size fields. ob_size (from PyVarObject) is the logical length visible to Python code. ob_alloc tracks how many pointer slots are actually allocated in ob_item. The two diverge whenever list.append or list.__init__ over-allocates to amortise future growth.

// CPython: Include/internal/pycore_list.h:30 PyListObject ob_alloc
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item; /* pointer to array of items */
Py_ssize_t ob_alloc; /* number of allocated slots */
} PyListObject;

_PyList_ITEMS bypasses the public PyList_GET_ITEM macro and hands back the raw ob_item pointer directly, avoiding a bounds-check path in hot inner loops inside the interpreter.

// CPython: Include/internal/pycore_list.h:40 _PyList_ITEMS
#define _PyList_ITEMS(op) (((PyListObject *)(op))->ob_item)

PyList_MAXFREELIST caps how many deallocated PyListObject structs (without their item arrays) are cached for reuse. The item arrays are always freed immediately; only the struct header is pooled.

// CPython: Include/internal/pycore_list.h:50 PyList_MAXFREELIST
#define PyList_MAXFREELIST 80

gopy notes

Status: not yet ported.

The list object lives in objects/list.go (the public struct) and objects/list_iter.go (the iterator). The internal fields ob_alloc and the _PyList_ITEMS access pattern are collapsed into Go slice headers for now. _Py_memory_repeat has no direct equivalent; list.copy uses append over a pre-allocated slice. PyList_MAXFREELIST is not implemented because Go's allocator handles pooling through sync.Pool when it becomes worthwhile.

Planned package path: objects (list struct) and objects (list iterator).