Skip to main content

Objects/odictobject.c (part 3)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/odictobject.c

This annotation covers OrderedDict-specific methods. See objects_odictobject2_detail for the doubly-linked list structure and __setitem__.

Map

LinesSymbolRole
1-80OrderedDict.move_to_endMove a key to the front or back
81-180OrderedDict.__reversed__Reverse-order iterator
181-280OrderedDict.popitemRemove and return the last (or first) item
281-400odict_mergeMerge while preserving order
401-500OrderedDict.__eq__Order-sensitive equality

Reading

OrderedDict.move_to_end

// CPython: Objects/odictobject.c:780 odict_move_to_end
static PyObject *
odict_move_to_end(PyObject *self, PyObject *args)
{
PyObject *key;
int last = 1;
if (!PyArg_ParseTuple(args, "O|p:move_to_end", &key, &last)) return NULL;
_ODictNode *node = _odict_find_node((PyODictObject *)self, key);
if (node == NULL) {
PyErr_SetObject(PyExc_KeyError, key);
return NULL;
}
/* Remove from current position */
_odict_remove_node((PyODictObject *)self, node);
/* Reinsert at end or beginning */
if (last)
_odict_add_tail((PyODictObject *)self, node);
else
_odict_add_head((PyODictObject *)self, node);
Py_RETURN_NONE;
}

od.move_to_end('key') moves the key to the back (default). od.move_to_end('key', last=False) moves it to the front. This is O(1): it just re-links the node in the doubly-linked list.

OrderedDict.__reversed__

// CPython: Objects/odictobject.c:860 odict_iter_reversed
static PyObject *
odict_iter_reversed(PyObject *self, PyObject *Py_UNUSED(ignored))
{
return odictiterobject_new((PyODictObject *)self, 1 /*reversed*/);
}

static PyObject *
odictiter_iternext(odictiterobject *di)
{
if (di->di_current == NULL) return NULL;
_ODictNode *node = di->di_current;
di->di_current = di->reversed ? node->od_prev : node->od_next;
return ... /* key, value, or key-value pair */;
}

reversed(od) returns an iterator that walks the doubly-linked list backward. The iterator stores a pointer to the current node; od_prev is followed at each step. Mutation during iteration raises RuntimeError (version tag check).

OrderedDict.popitem

// CPython: Objects/odictobject.c:820 odict_popitem
static PyObject *
odict_popitem(PyObject *self, PyObject *args)
{
int last = 1;
if (!PyArg_ParseTuple(args, "|p:popitem", &last)) return NULL;
PyODictObject *od = (PyODictObject *)self;
if (PyODict_SIZE(od) == 0) {
PyErr_SetString(PyExc_KeyError, "dictionary is empty");
return NULL;
}
_ODictNode *node = last ? od->od_last : od->od_first;
PyObject *key = node->od_key;
PyObject *value = PyODict_GetItemWithError(self, key);
PyODict_DelItem(self, key);
return PyTuple_Pack(2, key, value);
}

od.popitem() removes and returns the (key, value) pair for the last inserted key (LIFO). od.popitem(last=False) removes the first key (FIFO). Together with move_to_end, these allow OrderedDict to implement an LRU cache.

OrderedDict.__eq__

// CPython: Objects/odictobject.c:920 odict_richcompare
static PyObject *
odict_richcompare(PyObject *v, PyObject *w, int op)
{
if (op != Py_EQ && op != Py_NE) Py_RETURN_NOTIMPLEMENTED;
if (!PyODict_Check(v) || !PyODict_Check(w)) {
/* OrderedDict vs plain dict: order does not matter */
return PyDict_Type.tp_richcompare(v, w, op);
}
/* Both OrderedDicts: compare content AND order */
if (PyODict_SIZE(v) != PyODict_SIZE(w)) {
return PyBool_FromLong(op == Py_NE);
}
/* Walk both linked lists simultaneously */
_ODictNode *vn = ((PyODictObject *)v)->od_first;
_ODictNode *wn = ((PyODictObject *)w)->od_first;
while (vn != NULL) {
if (!PyObject_RichCompareBool(vn->od_key, wn->od_key, Py_EQ)) {
return PyBool_FromLong(op == Py_NE);
}
vn = vn->od_next; wn = wn->od_next;
}
/* Same order: delegate to dict equality for values */
return PyDict_Type.tp_richcompare(v, w, op);
}

OrderedDict({'a': 1, 'b': 2}) != OrderedDict({'b': 2, 'a': 1}) — insertion order matters for OrderedDict == OrderedDict. But OrderedDict({'a': 1}) == {'a': 1} — comparison with a plain dict ignores order.

gopy notes

OrderedDict.move_to_end is objects.ODictMoveToEnd in objects/odictobject.go. The node re-linking updates od_prev/od_next pointers. __reversed__ is objects.ODictReversed returning an objects.ODictIterReversed. popitem is objects.ODictPopItem. __eq__ walks the linked list using objects.ODictFirst / objects.NodeNext.