Skip to main content

Include/internal/pycore_list.h

Source:

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

pycore_list.h exposes the internal list object layout and the free-list optimization for small lists.

Map

LinesSymbolRole
1-30PyListObjectob_item pointer + allocated capacity
31-50_PyList_ITEMSZero-overhead macro to access ob_item
51-70_Py_LIST_INITIAL_CAPACITYGrowth pattern constants
71-80Free list_Py_list_state with freelist for empty lists

Reading

PyListObject

// CPython: Include/internal/pycore_list.h:12 PyListObject
typedef struct {
PyObject_VAR_HEAD /* ob_size = current length */
PyObject **ob_item; /* pointer to element array */
Py_ssize_t allocated; /* allocated capacity (>= ob_size) */
} PyListObject;

ob_size is the current number of elements; allocated is the capacity. When ob_size == allocated, the next append triggers a reallocation.

_PyList_ITEMS

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

PyList_GET_ITEM(op, i) is _PyList_ITEMS(op)[i] — a single array dereference with no bounds check.

Growth pattern

// CPython: Objects/listobject.c:60 list_resize
/* Growth formula: new_allocated = (ob_size >> 3) + (ob_size < 9 ? 3 : 6) */
/* This gives approximately: 0,4,8,16,25,35,46,58,72,88,... */
/* Over-allocation amortizes the O(n) realloc cost across O(1) appends */

For a list of 8 elements, append allocates capacity 16. For 16, it allocates 25, etc. The formula keeps the over-allocation factor under 12.5%.

Free list

// CPython: Include/internal/pycore_list.h:62 _Py_list_state
struct _Py_list_state {
/* Cache empty list structs to avoid malloc/free on frequent create/discard */
PyListObject *free_list[PyList_MAXFREELIST];
int numfree;
};

When a list is deallocated (but not its element array), its PyListObject shell is saved in the free list. The next PyList_New() reuses it. PyList_MAXFREELIST is 80.

gopy notes

list in gopy is objects/list.go backed by a Go []PyObject slice. Go's slice already provides the over-allocation behavior: append uses a similar growth factor. The _PyList_ITEMS macro maps to direct slice indexing. The free list optimization is not replicated; Go's runtime handles allocator efficiency.