Skip to main content

Objects/typeobject.c

cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c

The largest file in CPython. PyType_Type is the type object for type objects themselves. This file implements: type creation (type_new), the C3 MRO linearization (mro_internal), slot inheritance (inherit_slots, fixup_slot_dispatchers), descriptor lookup (_PyType_Lookup), attribute access on types (type_getattro), and the __subclasshook__ / __instancecheck__ protocol. The slot wrappers that expose C slots as Python methods (slot_tp_repr, slot_nb_add, etc.) are also here.

Map

LinesSymbolRolegopy
50-120type_reprrepr(SomeType) via tp_repr.objects/type.go:typeRepr
121-200type_calltype(obj) and type(name, bases, ns) dispatch.objects/type_call.go:TypeCall
201-300type_getattroAttribute lookup on a type: MRO search then data descriptor check.objects/type.go:TypeGetAttro
301-380type_setattroAttribute write on a type; invalidates tp_version_tag.objects/type.go:TypeSetAttro
381-420type_traverse, type_clear, type_deallocGC traversal, cycle break, and deallocation for type objects.objects/type.go
421-550_PyType_LookupMRO attribute search with inline cache keyed on (type, name, version_tag).objects/type.go:(*Type).Lookup
551-650_PyType_LookupId, _PyType_LookupRefVariants of _PyType_Lookup for C-string keys and borrowed-ref-safe callers.objects/type.go
651-800_PyType_CacheTag, _PyType_ModifiedVersion-tag management; bump and propagate on mutation.objects/type.go:Modified
801-950type_new_alloc, type_new_set_slotsAllocate the new type object; validate and lay out __slots__.objects/type.go
951-1100type_new_descriptorsBuild slot descriptor objects for every __slots__ entry.objects/type.go:typeNewDescriptors
1101-1200type_new_set_namesWalk tp_dict and call __set_name__ on each descriptor.objects/type.go:typeNewSetNames
1201-1350type_new_init_subclassCall __init_subclass__ on every base passing keyword arguments from the class statement.objects/type.go:typeNewInitSubclass
1351-2000type_newFull type.__new__(mcs, name, bases, ns, **kw) implementation.objects/type.go:typeNew
2001-2200calculate_metaclassSelect the most-derived metaclass from bases; raise TypeError on conflict.objects/type.go:calculateMetaclass
2201-2500mro_internal, mro_implementationC3 linearization entry points; return an ordered tuple of types.objects/type.go:mroInternal
2501-2800mro_merge, mro_check_duplicatesInner C3 merge loop; detects cycles and inconsistencies.objects/type.go:mroMerge
2801-3200type_ready_check_dict, type_ready_fill_dictFill tp_dict with wrappers for inherited slots before freezing.objects/type.go
3201-3800type_ready, PyType_ReadyFull type finalization: set tp_base, build MRO, inherit slots, populate tp_dict.objects/type.go:Ready
3801-4500inherit_slots, inherit_specialCopy NULL slot pointers from base to derived type.objects/type.go:inheritSlots
4501-5000fixup_slot_dispatchers, update_all_slots, update_one_slotInstall or refresh slot_* wrapper functions after any class dict mutation.objects/type.go:fixupSlotDispatchers
5001-6000slot_tp_repr, slot_tp_hash, slot_tp_call, slot_tp_strtp_* wrappers: call the Python __dunder__ via vectorcall.objects/type.go
6001-7000slot_nb_add, slot_nb_subtract, slot_nb_multiply, slot_nb_raddNumeric slot wrappers; reversed ops try the right-hand type first for subtypes.objects/type.go
7001-8000PyObject_GenericGetDict, PyObject_GenericSetDict, _PyObjectDict_SetItemGeneric instance __dict__ management.objects/object.go:GenericGetDict
8001-9000subtype_setdict, subtype_getweakref, type_get_docSubtype-specific slot implementations for __dict__, __weakref__, __doc__.objects/usertype.go
9001-9800super_getattro, super_descr_get, super_initsuper() implementation: MRO index search and attribute lookup one slot past the pivot.objects/super.go
9801-10500object_init, object_new, object_repr, object_hashSlots for object itself (the root base class).objects/object.go
10501-11000type___instancecheck___impl, type___subclasscheck___implisinstance / issubclass with __instancecheck__ and __subclasshook__ support.objects/type.go
11001-11500__init_subclass__ default, __class_getitem__ defaultDefault implementations; __class_getitem__ returns a GenericAlias.objects/type.go, objects/generic_alias.go
11501-12302PyType_AddWatcher, PyType_ClearWatcher, PyType_Watch, PyType_UnwatchType watcher API for inline cache invalidation by the specializing interpreter.objects/type.go:AddWatcher

Reading

_PyType_Lookup (lines 421 to 550)

cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L421-550

The hottest function in the interpreter for attribute-heavy workloads. Walks tp_mro from left to right, checks each type's tp_dict for name. Before the walk it probes a global fixed-size hash table (the "lookup cache") keyed on (type, name). The entry also stores the tp_version_tag of the type at the time it was cached; a version mismatch forces a full MRO walk and updates the cache.

PyObject *
_PyType_Lookup(PyTypeObject *type, PyObject *name)
{
PyObject *mro = type->tp_mro;
Py_ssize_t n = PyTuple_GET_SIZE(mro);
for (Py_ssize_t i = 0; i < n; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(mro, i);
PyObject *res = PyDict_GetItemWithError(base->tp_dict, name);
if (res != NULL) {
return res;
}
}
return NULL;
}

The actual implementation wraps this loop with the version-tag cache check. On a cache hit the version tag of the cached type is compared with the current type->tp_version_tag; if they match the cached result is returned without touching any dict. Cache entries are invalidated whenever tp_version_tag changes, which happens in _PyType_Modified after any mutation of a type or its bases.

type_new (lines 1351 to 2000)

cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L1351-2000

type(name, bases, namespace) implemented in C. The sequence of operations:

  1. Validate that bases is a non-empty tuple of types.
  2. Call calculate_metaclass to find the most-derived metaclass among all bases.
  3. If the winning metaclass is not type itself, forward to metaclass(name, bases, ns) and return.
  4. Allocate the type object via type_new_alloc, compute layout from __slots__.
  5. Copy entries from the user-supplied namespace into tp_dict.
  6. Call type_new_descriptors to build member_descriptor objects for __slots__.
  7. Call PyType_Ready to fill inherited slots and build the MRO.
  8. Call type_new_set_names to invoke __set_name__(owner, name) on each descriptor that defines it.
  9. Call type_new_init_subclass to invoke __init_subclass__ on each base, forwarding any keyword arguments from the class statement.

calculate_metaclass selects the metaclass:

static PyTypeObject *
calculate_metaclass(PyTypeObject *metatype, PyObject *bases)
{
PyTypeObject *winner = metatype;
Py_ssize_t nbases = PyTuple_GET_SIZE(bases);
for (Py_ssize_t i = 0; i < nbases; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i);
PyTypeObject *basemet = Py_TYPE(base);
if (PyType_IsSubtype(winner, basemet))
continue;
if (PyType_IsSubtype(basemet, winner)) {
winner = basemet;
continue;
}
PyErr_SetString(PyExc_TypeError,
"metaclass conflict: the metaclass of a derived class "
"must be a (non-strict) subclass of the metaclasses of all its bases");
return NULL;
}
return winner;
}

MRO: mro_internal (lines 2201 to 2500)

cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L2201-2500

C3 linearization. Builds a list of lists: each direct base's existing MRO plus the bases tuple itself. The merge loop repeatedly picks the head of the first list whose head does not appear in the tail of any other list:

// Pseudocode for mro_merge
lists = [base.mro for base in bases] + [list(bases)]
result = [type]
while any list is non-empty:
for lst in lists:
candidate = lst[0]
if candidate not in [l[1:] for l in lists]:
result.append(candidate)
for lst in lists:
if lst[0] == candidate:
lst.pop(0)
break
else:
raise TypeError("Cannot create a consistent method resolution order")

If no candidate is found in a full sweep, the bases have an inconsistent ordering and TypeError is raised. The result is stored as type->tp_mro, a tuple borrowed by all subsequent _PyType_Lookup calls.

PyType_Ready (lines 3201 to 3800)

cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L3201-3800

Called once per statically defined C type at module or interpreter initialization. The critical operations:

  1. Set tp_base to &PyBaseObject_Type if still NULL.
  2. Call PyType_Ready recursively on tp_base and each element of tp_bases if they are not yet ready.
  3. Compute tp_mro via mro_internal.
  4. Call inherit_slots to copy NULL slot pointers from the base type.
  5. Populate tp_dict with slot_wrapper and method_descriptor objects for every slot the type exposes.
  6. Set Py_TPFLAGS_READY on the type.

After PyType_Ready a C type is immutable from the C side. Python subclassing goes through type_new instead, which calls PyType_Ready on the newly created type before returning.

Slot wrappers (lines 5001 to 7000)

cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L5001-7000

When a Python class defines __repr__, CPython installs slot_tp_repr into tp->tp_repr. That C function then calls self.__repr__() via _PyObject_CallMethodNoArgs. The numeric wrappers are more complex because they handle reflected operations:

static PyObject *
slot_nb_add(PyObject *self, PyObject *other)
{
PyObject *r;
// If type(other) is a subtype of type(self), try other.__radd__ first.
if (!Py_IS_TYPE(self, Py_TYPE(other)) &&
PyType_IsSubtype(Py_TYPE(other), Py_TYPE(self))) {
r = call_maybe(other, &_Py_ID(__radd__), self);
if (r != Py_NotImplemented)
return r;
Py_DECREF(r);
}
r = call_maybe(self, &_Py_ID(__add__), other);
if (r != Py_NotImplemented)
return r;
Py_DECREF(r);
r = call_maybe(other, &_Py_ID(__radd__), self);
return r;
}

fixup_slot_dispatchers is called whenever a class __dict__ changes: it walks every slot and (re-)installs the appropriate slot_* function or NULL depending on whether the corresponding __dunder__ is defined anywhere in the MRO.

super_getattro (lines 9001 to 9500)

cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L9001-9500

super(type, obj).__getattribute__(name) starts the MRO search one entry past type in obj's MRO. The pivot search:

// Find the index of 'type' in mro(type(obj)):
PyObject *mro = Py_TYPE(obj)->tp_mro;
Py_ssize_t n = PyTuple_GET_SIZE(mro);
Py_ssize_t i;
for (i = 0; i < n; i++) {
if ((PyObject *)type == PyTuple_GET_ITEM(mro, i))
break;
}
// Walk from i+1 onward:
for (i++; i < n; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(mro, i);
PyObject *res = PyDict_GetItemWithError(base->tp_dict, name);
if (res != NULL) {
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
if (f != NULL)
res = f(res, obj, (PyObject *)Py_TYPE(obj));
return res;
}
}

If the name is found, descriptors are invoked normally. This is the entire mechanism behind super().method(): no magic beyond an MRO scan starting one slot after the pivot type.

gopy mirror

objects/type.go, objects/type_call.go, objects/usertype.go, objects/super.go. _PyType_Lookup maps to (*Type).Lookup. The MRO is a Go []*Type slice. Slot wrappers are generated by gen/slots/ from Include/cpython/typeslots.h. PyType_Ready maps to (*Type).Ready.

CPython 3.14 changes

__class_getitem__ protocol stabilized (returns GenericAlias by default). Type watcher API (PyType_AddWatcher) added in 3.12; improved overflow handling in 3.13. tp_version_tag overflow handling improved in 3.13. __init_subclass__ keyword-argument forwarding has been stable since 3.6. PEP 649 adds __annotations__ lazy evaluation, changing how annotation descriptors in class bodies interact with type_new.