Skip to main content

pycore_list.h: internal list layout

pycore_list.h exposes the internal fields of PyListObject that are hidden behind the stable ABI. It also declares the iterator struct and the per-interpreter free list that recycles small list headers.

Map

LinesSymbolKindPurpose
1–20PyListObjectstructlist header (ob_item pointer, ob_size, allocated)
21–40_PyListIterObjectstructforward iterator (it_seq, it_index)
41–55_PyListRevIterObjectstructreverse iterator (it_seq, it_index)
56–70_Py_list_freeliststructper-interpreter free-list state
71–80_PyList_NotifyEventenumwatcher event codes (append, insert, item_set, ...)

Reading

PyListObject struct

typedef struct {
PyObject_VAR_HEAD /* ob_refcnt, ob_type, ob_size */
PyObject **ob_item; /* pointer to item array on the heap */
Py_ssize_t allocated; /* slots allocated (>= ob_size) */
} PyListObject;

ob_size is the logical length; allocated is the capacity of the ob_item array. The growth formula keeps allocated between ob_size and roughly ob_size * 9/8 + 6, so appends are amortized O(1).

Iterator structs

Both forward and reverse iterators hold a borrowed reference to the list and a current index. Mutation of the underlying list is not detected at the C level; user code must not rely on iterator safety.

typedef struct {
PyObject_HEAD
Py_ssize_t it_index;
PyListObject *it_seq; /* NULL when exhausted */
} _PyListIterObject;

Free list and watchers

CPython keeps up to PyList_MAXFREELIST (80 in 3.14) dead PyListObject headers per interpreter. When a list is deallocated its header goes onto the free list; the next PyList_New pops from it, skipping the allocator. The ob_item buffer is always freed separately.

_PyList_NotifyEvent codes include PyList_EVENT_APPEND, PyList_EVENT_INSERT, PyList_EVENT_ITEM_SET, PyList_EVENT_CLEAR, and PyList_EVENT_DEALLOCATED. Watchers are registered per-interpreter via PyList_AddWatcher.

gopy notes

objects/list.go stores items in a Go slice, so ob_item and allocated map to the slice's backing array and cap. The free list is not implemented since Go's GC manages allocation. Watcher events are partially wired: APPEND and ITEM_SET fire today; INSERT, CLEAR, and DEALLOCATED are stubs that return nil. The iterator structs map to listIterator and listRevIterator in the same file.