Skip to main content

Objects/typeobject.c (part 10)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c

This annotation covers MRO computation and subclass hooks. See objects_typeobject9_detail for type.__new__, type.__init__, and the type creation path.

Map

LinesSymbolRole
1-80type.mroReturn type.__mro__ as a tuple
81-200mro_internalCompute C3 linearization
201-320pmergeC3 merge step
321-420type.__init_subclass__Hook called when a class is subclassed
421-600type.__class_getitem__list[int] syntax via __class_getitem__

Reading

C3 linearization

// CPython: Objects/typeobject.c:2140 mro_internal
static PyObject *
mro_internal(PyTypeObject *type, PyObject **p_old_mro)
{
/* C3: result = [type] + merge(bases[0].mro, bases[1].mro, ..., [bases]) */
PyObject *bases = type->tp_bases;
Py_ssize_t n = PyTuple_GET_SIZE(bases);

PyObject **to_merge = PyMem_New(PyObject *, n + 1);
for (int i = 0; i < n; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i);
to_merge[i] = PySequence_List(base->tp_mro);
}
to_merge[n] = PySequence_List(bases); /* append bases list */
PyObject *result = PyList_New(1);
PyList_SET_ITEM(result, 0, (PyObject *)type);
if (pmerge(result, to_merge, n + 1) < 0) { ... }
return PyList_AsTuple(result);
}

The C3 algorithm builds an MRO that satisfies: (1) each class appears before its bases, (2) the local precedence order from __bases__ is preserved, (3) the monotonicity constraint. pmerge implements the "take good head" rule.

pmerge

// CPython: Objects/typeobject.c:2080 pmerge
static int
pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size)
{
for (;;) {
int empty_cnt = 0;
for (int i = 0; i < to_merge_size; i++) {
PyObject *candidate = PyList_GET_ITEM(to_merge[i], 0);
/* Candidate is good if it doesn't appear in the tail of any list */
int is_in_tail = 0;
for (int j = 0; j < to_merge_size; j++) {
if (tail_contains(to_merge[j], candidate)) {
is_in_tail = 1; break;
}
}
if (!is_in_tail) {
PyList_Append(acc, candidate);
/* Remove candidate from all list heads */
for (int j = 0; j < to_merge_size; j++)
if (head_equals(to_merge[j], candidate))
remove_head(to_merge[j]);
goto restart;
}
}
if (empty_cnt == to_merge_size) break;
PyErr_SetString(PyExc_TypeError, "Cannot create a consistent method resolution order...");
return -1;
restart:;
}
return 0;
}

pmerge takes the first element of the first list whose value doesn't appear in the tail of any list. This element is appended to the result and removed from all list heads. If no such element exists, the MRO is inconsistent (raises TypeError).

type.__init_subclass__

// CPython: Objects/typeobject.c:7840 type_init_subclass
static PyObject *
type_init_subclass(PyObject *cls, PyObject *args, PyObject *kwds)
{
/* Called by type.__init__ after a new class is created:
walk bases and call their __init_subclass__ */
PyObject *super = PyObject_CallOneArg((PyObject *)&PySuper_Type, cls);
PyObject *func = PyObject_GetAttr(super, &_Py_ID(__init_subclass__));
return PyObject_Call(func, args, kwds);
}

class Foo(Base, keyword=value) calls Base.__init_subclass__(keyword=value) after Foo is created. This is the primary mechanism for class decorators that need to run at class definition time.

gopy notes

mro_internal is objects.TypeComputeMRO in objects/type.go. It calls pmerge implemented as a Go function with [][]objects.Object slices. type.__init_subclass__ calls objects.CallMethod on the super object. type.__class_getitem__ returns a objects.GenericAlias.