Skip to main content

Python/ceval.c (part 71)

Source:

cpython 3.14 @ ab2d84fe1023/Python/ceval.c

This annotation covers subscript get/set/delete opcodes and their adaptive specializations. See python_ceval70_detail for LOAD_FAST, LOAD_FAST_CHECK, and DELETE_FAST.

Map

LinesSymbolRole
1-80BINARY_SUBSCRa[i] — call __getitem__
81-180BINARY_SUBSCR_LIST_INTSpecialization for list + int index
181-280BINARY_SUBSCR_DICTSpecialization for dict lookup
281-380STORE_SUBSCRa[i] = v — call __setitem__
381-500DELETE_SUBSCRdel a[i] — call __delitem__

Reading

BINARY_SUBSCR

// CPython: Python/ceval.c:2180 BINARY_SUBSCR
inst(BINARY_SUBSCR, (container, sub -- res)) {
res = PyObject_GetItem(container, sub);
ERROR_IF(res == NULL, error);
Py_DECREF(container);
Py_DECREF(sub);
}

a[i] compiles to LOAD_NAME a, LOAD_NAME i, BINARY_SUBSCR. PyObject_GetItem dispatches to tp_as_mapping->mp_subscript or tp_as_sequence->sq_item depending on the type.

BINARY_SUBSCR_LIST_INT

// CPython: Python/ceval.c:2210 BINARY_SUBSCR_LIST_INT
inst(BINARY_SUBSCR_LIST_INT, (list, sub -- res)) {
DEOPT_IF(!PyList_CheckExact(list));
DEOPT_IF(!PyLong_CheckExact(sub));
Py_ssize_t index = PyLong_AsLong(sub);
DEOPT_IF(index < 0);
DEOPT_IF(index >= PyList_GET_SIZE(list));
res = Py_NewRef(PyList_GET_ITEM(list, index));
Py_DECREF(list);
Py_DECREF(sub);
}

When BINARY_SUBSCR is specialized for list[int], it bypasses PyObject_GetItem entirely: it reads ob_item[index] directly after bounds checking. Negative indices are not handled in the fast path — they deopt to the generic form.

BINARY_SUBSCR_DICT

// CPython: Python/ceval.c:2250 BINARY_SUBSCR_DICT
inst(BINARY_SUBSCR_DICT, (dict, sub -- res)) {
DEOPT_IF(!PyDict_CheckExact(dict));
res = PyDict_GetItemWithError(dict, sub);
if (res == NULL) {
if (!_PyErr_Occurred(tstate)) {
_PyErr_SetKeyError(sub);
}
goto error;
}
Py_INCREF(res);
Py_DECREF(dict);
Py_DECREF(sub);
}

BINARY_SUBSCR_DICT calls PyDict_GetItemWithError instead of the generic mapping protocol. This skips the tp_as_mapping indirection. A missing key still raises KeyError; None is a valid value (unlike PyDict_GetItem which returns NULL for both missing and error).

STORE_SUBSCR

// CPython: Python/ceval.c:2320 STORE_SUBSCR
inst(STORE_SUBSCR, (v, container, sub --)) {
/* container[sub] = v */
int err = PyObject_SetItem(container, sub, v);
Py_DECREF(v);
Py_DECREF(container);
Py_DECREF(sub);
ERROR_IF(err != 0, error);
}

Stack order: v is pushed first (it's the value being assigned), then container, then sub. So a[i] = v compiles as: LOAD v, LOAD a, LOAD i, STORE_SUBSCR. The reversed-from-source stack layout is a recurring CPython pattern.

DELETE_SUBSCR

// CPython: Python/ceval.c:2360 DELETE_SUBSCR
inst(DELETE_SUBSCR, (container, sub --)) {
/* del container[sub] */
int err = PyObject_DelItem(container, sub);
Py_DECREF(container);
Py_DECREF(sub);
ERROR_IF(err != 0, error);
}

del a[i] delegates to tp_as_mapping->mp_ass_subscript(container, sub, NULL). Passing NULL as the value signals deletion in the mapping protocol.

gopy notes

BINARY_SUBSCR is in vm/eval_simple.go and calls objects.GetItem. BINARY_SUBSCR_LIST_INT reads objects.List.Items[index] directly. BINARY_SUBSCR_DICT calls objects.DictGetItemWithError. STORE_SUBSCR calls objects.SetItem. DELETE_SUBSCR calls objects.DelItem.