Skip to main content

Modules/_collectionsmodule.c

cpython 3.14 @ ab2d84fe1023/Modules/_collectionsmodule.c

The _collections built-in module contains the C implementation of collections.deque. Lib/collections/__init__.py imports deque from _collections and re-exports it as the public collections.deque.

A deque ("double-ended queue") supports O(1) amortized append and pop from either end. The implementation achieves this by storing items in a doubly-linked list of fixed-size arrays called blocks. Blocks hold BLOCKLEN = 64 items each; the deque only allocates or frees a block when a boundary is crossed.

The file also provides _collections._count_elements, a C-speed helper used by collections.Counter to tally elements from an iterable into a dict.

Map

LinesSymbolRolegopy
1-80BLOCKLEN, block, blockindexBlock struct and index arithmetic constants.module/collections/module.go:block
80-300dequeobject, deque_new, deque_init, deque_clearDeque type layout: left/right block pointers, leftindex, rightindex, len, maxlen.module/collections/module.go:Deque
300-500deque_append, deque_appendleftAdd to tail/head; allocate a new block when the current is full.module/collections/module.go:Append
500-650deque_pop, deque_popleftRemove from tail/head; free block when empty.module/collections/module.go:Pop
650-900deque_rotateRotate N steps; moves whole blocks when `N
900-1200deque_extend, deque_extendleft, deque_insert, deque_remove, deque_index, deque_countBulk and search operations.module/collections/module.go:Extend
1200-1600deque_iter, dequeiterobject, dequeiter_next, dequereviterForward and reverse iterators; O(1) per step using block + index cursor.module/collections/module.go:DequeIter
1600-1900deque_getitem, deque_setitem, deque_delitem, deque_richcompare, deque_repr, deque_sizeofSequence protocol, comparison, and introspection.module/collections/module.go:DequeSeq
1900-2200_count_elements, deque_type, _collectionsmodule, PyInit__collectionsCounter helper, type object, module definition and entry point.module/collections/module.go:CountElements

Reading

Block layout and deque_append (lines 1 to 500)

cpython 3.14 @ ab2d84fe1023/Modules/_collectionsmodule.c#L1-500

Each block is a small struct holding a prev/next pointer pair and a fixed-size item array:

#define BLOCKLEN 64

typedef struct BLOCK {
struct BLOCK *leftlink;
PyObject *data[BLOCKLEN];
struct BLOCK *rightlink;
} block;

dequeobject keeps a leftblock/rightblock pair and two index cursors leftindex (next read position at the left end) and rightindex (last write position at the right end):

typedef struct {
PyObject_VAR_HEAD
block *leftblock;
block *rightblock;
Py_ssize_t leftindex; /* 0 <= leftindex < BLOCKLEN */
Py_ssize_t rightindex; /* 0 <= rightindex < BLOCKLEN */
uint64_t state; /* incremented on mutation */
Py_ssize_t maxlen; /* -1 means unbounded */
PyObject *weakreflist;
} dequeobject;

deque_append writes to rightindex + 1. When rightindex reaches BLOCKLEN - 1, a new block is allocated and linked:

static PyObject *
deque_append(dequeobject *deque, PyObject *item)
{
if (deque->rightindex == BLOCKLEN - 1) {
block *b = newblock();
if (b == NULL) return NULL;
b->leftlink = deque->rightblock;
deque->rightblock->rightlink = b;
deque->rightblock = b;
deque->rightindex = -1;
}
Py_INCREF(item);
deque->len++;
deque->rightindex++;
deque->rightblock->data[deque->rightindex] = item;
TRIM(deque, deque_popleft); /* enforce maxlen */
Py_RETURN_NONE;
}

TRIM is a macro that calls deque_popleft (or deque_pop for appendleft) when deque->len > deque->maxlen. This enforces the bounded deque contract in O(1) without an extra conditional branch in the hot path.

deque_rotate (lines 650 to 900)

cpython 3.14 @ ab2d84fe1023/Modules/_collectionsmodule.c#L650-900

deque.rotate(n) moves n items from the right end to the left end (or vice versa for negative n). The naive approach would call append/pop |n| times. CPython optimises large rotations by moving whole blocks at once when |n| >= BLOCKLEN:

static PyObject *
deque_rotate(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs)
{
Py_ssize_t n = 1;
/* ... parse n ... */

/* Optimisation: move whole blocks for large rotations. */
while (n > 0) {
if (deque->leftindex == 0) {
block *b = deque->rightblock; /* steal from right */
deque->rightblock = b->leftlink;
deque->rightblock->rightlink = NULL;
b->rightlink = deque->leftblock;
deque->leftblock->leftlink = b;
deque->leftblock = b;
deque->leftindex = BLOCKLEN;
}
m = MIN(n, deque->leftindex); /* items to move in this pass */
deque->leftindex -= m;
deque->rightindex -= m;
/* ... memmove items within the block ... */
n -= m;
}
/* symmetric loop for n < 0 */
...
}

For small |n| (less than BLOCKLEN) the function falls through to an item-by-item loop that calls deque_pop / deque_appendleft (or the mirror pair). The block-move path avoids per-item reference count churn and makes rotate effectively O(|n| / BLOCKLEN) in terms of memory operations for large n.

maxlen auto-eviction (lines 300 to 500)

cpython 3.14 @ ab2d84fe1023/Modules/_collectionsmodule.c#L300-500

When maxlen is set, the deque is bounded. deque_append enforces this with the TRIM macro:

#define TRIM(d, popfunction) \
if (Py_SIZE(d) > d->maxlen) { \
PyObject *rv = popfunction(d, NULL); \
assert(rv != NULL); \
assert(PyNone_Check(rv)); \
Py_DECREF(rv); \
}

deque_append passes deque_popleft as the pop function (evict from the opposite end), while deque_appendleft passes deque_pop. The macro fires at most once per append because maxlen constrains the size to never exceed maxlen + 1 before the trim. This gives the bounded deque its "sliding window" semantics: appending to a full deque atomically advances both ends.

_count_elements (lines 1900 to 2000)

cpython 3.14 @ ab2d84fe1023/Modules/_collectionsmodule.c#L1900-2000

_count_elements(mapping, iterable) is a tight C loop that increments mapping[key] for each item from the iterable, used as the inner loop of Counter.__init__ and Counter.update:

static PyObject *
_count_elements(PyObject *self, PyObject *args)
{
PyObject *it, *mapping, *key, *count, *oldval;
/* ... */
while ((key = iternext(it))) {
oldval = PyObject_GetItem(mapping, key);
if (oldval == NULL) {
PyErr_Clear();
PyObject_SetItem(mapping, key, _PyLong_One);
} else {
count = PyNumber_Add(oldval, _PyLong_One);
PyObject_SetItem(mapping, key, count);
/* ... */
}
Py_DECREF(key);
}
/* ... */
}

The function avoids going through Python-level __getitem__ / __setitem__ dispatch for the common case where mapping is a plain dict, using the PyDict_GetItemWithError / PyDict_SetItem fast path instead.

deque_iter (lines 1200 to 1600)

cpython 3.14 @ ab2d84fe1023/Modules/_collectionsmodule.c#L1200-1600

The iterator object stores a block pointer and an index rather than a Python integer position, so advancing is a single pointer dereference plus an increment with no division:

typedef struct {
PyObject_HEAD
block *b;
Py_ssize_t index;
dequeobject *deque;
uint64_t state; /* detect mutation during iteration */
Py_ssize_t counter; /* items remaining */
} dequeiterobject;

static PyObject *
dequeiter_next(dequeiterobject *it)
{
if (it->b == NULL || it->counter == 0)
return NULL; /* exhausted */
if (it->deque->state != it->state) {
PyErr_SetString(PyExc_RuntimeError,
"deque changed size during iteration");
return NULL;
}
PyObject *item = it->b->data[it->index];
it->index++;
if (it->index == BLOCKLEN) {
it->b = it->b->rightlink;
it->index = 0;
}
it->counter--;
Py_INCREF(item);
return item;
}

The state field mirrors dequeobject.state, which is incremented on every mutation. Any modification to the deque while iterating raises RuntimeError: deque changed size during iteration.

gopy mirror

module/collections/module.go. block maps to a Go struct with a [64]*py.Object data array and left/right sibling pointers. Deque wraps left/right block pointers and index cursors. Append, Pop, Rotate, and Extend follow the block-chain logic above. CountElements is a direct port of _count_elements.

CPython 3.14 changes

deque has been a C type since Python 2.4. The iterator gained the mutation state check in 3.0. BLOCKLEN was increased from 62 to 64 in 3.5 to align the block struct to a cache line. Multi-phase module init was added in 3.12. The deque.__class_getitem__ classmethod (for deque[int] type hints) was added in 3.9 and is implemented in Objects/genericaliasobject.c, not here.