The type and object Relationship

At the very base of Python’s object system sit two special built-in classes whose relationship looks, at first glance, paradoxical. object is the root base class: every class’s method resolution order ends at object, and object itself has no base (object.__bases__ is the empty tuple). type is the root metaclass: every class — including object, and including type itself — is an instance of type, so type(type) is type. Combine these and you get a closed loop: type is an instance of type; object is an instance of type; and type is a subclass of object. No third object is needed to break the circle, and none exists. CPython resolves the apparent chicken-and-egg by hard-wiring the loop in C — partly as static initializers compiled into the binary, partly during interpreter startup in _PyTypes_InitTypes (Objects/object.c, as of CPython 3.14.5). This note untangles that loop, distinguishes the two independent “is-a” axes it lives on, and shows why this exact two-axis structure is what makes metaclasses possible at all.

This note is the companion to Metaclasses. The division of labour: this note owns the two-axis structure and the bootstrap — what object and type are, how the circular references are constructed, and how isinstance/issubclass/type() read the two axes. The sibling owns the class-creation pipeline — what happens when a class statement runs, the metaclass hooks, conflict resolution, and use cases. The note Type Objects and PyTypeObject covers the PyTypeObject C struct itself (the slot table) and explicitly defers the bootstrap story to here.

Mental Model — Two Axes, Not One

The single conceptual mistake that makes the type/object relationship feel paradoxical is collapsing two different relationships into one word, “is-a”. Python objects participate in two independent directed graphs that happen to be drawn over the same set of class objects:

  • The instance-of axis — “what is the type of this object?” Every Python object, including every class object, carries a pointer to the class it is an instance of. In CPython this is the ob_type field of the object header, read by type(x). For ordinary values: type(42) is int. For classes: type(int) is type, type(str) is type, and crucially type(type) is type. The instance-of axis, followed upward, always lands on type and stops there, because type is its own type.
  • The subclass-of (inheritance) axis — “what does this class inherit from?” Every class carries its bases (__bases__) and its linearized method resolution order (__mro__). For classes: int.__bases__ is (object,), type.__bases__ is (object,), and object.__bases__ is () — the empty tuple, because object is the root and inherits from nothing. The subclass-of axis, followed upward, always lands on object and stops there.

These two axes are orthogonal. int is an instance of type (instance-of axis) and inherits from object (subclass-of axis); neither fact implies the other. The reason the relationship feels circular is that object and type each sit at the top of one axis while being an ordinary participant in the other: object is the top of inheritance but is itself an instance of type; type is the top of instance-of but is itself a subclass of object.

graph TD
    subgraph instanceof ["instance-of axis (ob_type / type(x))"]
        direction LR
        u1["MyClass<br/>(a user class)"] -->|"type(MyClass)"| ty1["type"]
        o1["object"] -->|"type(object)"| ty1
        ty1 -->|"type(type) is type"| ty1
    end
    subgraph subclassof ["subclass-of axis (__bases__ / __mro__)"]
        direction LR
        u2["MyClass"] -->|"inherits"| o2["object"]
        ty2["type"] -->|"inherits"| o2
        o2 -.->|"__bases__ == ()"| nil["(nothing)"]
    end

Diagram: the same three class objects (object, type, a user class MyClass) drawn on both axes. Instance-of (top) flows toward type and loops at type; subclass-of (bottom) flows toward object and terminates. The insight: there is no contradiction because the two terminating nodes live on different axes — type terminates instance-of, object terminates subclass-of, and each is a normal node on the other’s axis.

You can confirm the whole structure at a REPL — these are not approximations, they are exact identities the interpreter guarantees:

>>> type(object)            # object is an instance of type
<class 'type'>
>>> type(type)              # type is an instance of itself — the loop
<class 'type'>
>>> type.__bases__          # type inherits from object
(<class 'object'>,)
>>> object.__bases__        # object inherits from nothing — the root
()
>>> issubclass(type, object)
True
>>> issubclass(object, type)
False

The last two lines are worth dwelling on. type is a subclass of object (every class is, since object is the universal base), but object is not a subclass of typeobject is merely an instance of type. Mixing those up is the canonical confusion this note exists to dispel.

Mechanical Walk-through — How CPython Bootstraps the Loop

A circular reference cannot be built by ordinary, sequential object construction: to make type an instance of type you would already need type to exist. CPython sidesteps this in two stages, at two different times, using two different mechanisms. Conflating these two stages is the most common error in informal explanations, so we keep them strictly separate.

Stage 1 — Static initializers (compile time)

object and type are not heap-allocated at startup like user classes; they are static PyTypeObject structs defined directly in C, so their storage and their header fields are fixed when the interpreter binary is compiled. The relevant fields are the object header’s type pointer, set by the PyVarObject_HEAD_INIT(type_ptr, size) macro, which writes type_ptr into the struct’s ob_type slot.

In Objects/typeobject.c (CPython 3.14.5), the type class is defined as:

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)   // ob_type = &PyType_Type  (type's type is type)
    "type",                                  // tp_name
    sizeof(PyHeapTypeObject),                // tp_basicsize
    ...
    0,                                       // tp_base  (NULL here — filled in at runtime)
    ...
    type_call,                               // tp_call  (makes `type(...)` callable)
    ...
    type_new,                                // tp_new   (the default metaclass constructor)
};

Line-by-line, the load-bearing parts:

  • PyVarObject_HEAD_INIT(&PyType_Type, 0) — this is the bootstrap’s keystone. It initializes PyType_Type’s own header to point its ob_type at &PyType_Type — the address of the very struct being defined. Taking the address of a not-yet-fully-initialized static object is legal in C (the storage exists at compile time), so this self-reference compiles cleanly. The runtime consequence is type(type) is type: the instance-of loop is baked into the binary, true before a single line of Python runs.
  • "type" / tp_basicsize — the class’s name and instance size; an instance of type is a class object, laid out as a PyHeapTypeObject.
  • 0, // tp_base — note type’s base is NULL in the static initializer. The subclass-of link from type to object is not set here; it is wired up later, in Stage 2.
  • type_call and type_new — the C functions that make type callable and that construct new classes. These are what turn type(name, bases, dict) and the class statement into class objects (the subject of Metaclasses).

The object class, PyBaseObject_Type, is defined the same way:

PyTypeObject PyBaseObject_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)   // ob_type = &PyType_Type  (object's type is type)
    "object",                                // tp_name
    sizeof(PyObject),                        // tp_basicsize
    ...
    0,                                       // tp_base  (NULL — and it stays NULL; object has no base)
    ...
    object_new,                              // tp_new
};
  • PyVarObject_HEAD_INIT(&PyType_Type, 0)object’s header points at type, so type(object) is type. Again, baked in statically.
  • 0, // tp_baseobject’s base is NULL and, unlike type, it stays NULL forever. This is the C-level encoding of “object.__bases__ == ()”: object is the root of the inheritance axis, the place every MRO terminates.

So after compilation, before any runtime initialization, the instance-of axis is already complete: object.ob_type == &PyType_Type and type.ob_type == &PyType_Type. What is not yet set is the subclass-of link type → object.

Stage 2 — _PyTypes_InitTypes (interpreter startup)

The subclass-of wiring, the MRO computation, slot inheritance, and the population of each class’s tp_dict happen at runtime, early in interpreter startup, in _PyTypes_InitTypes (Objects/object.c). This function walks a static array static_types[] and calls _PyStaticType_InitBuiltin (which runs PyType_Ready) on each. The array is deliberately ordered so that object and type come first, with a comment that says exactly why:

static PyTypeObject* static_types[] = {
    // The two most important base types: must be initialized first and
    // deallocated last.
    &PyBaseObject_Type,   // object
    &PyType_Type,         // type
    // Static types with base=&PyBaseObject_Type
    &PyAsyncGen_Type,
    ... ~150 more ...
};
 
PyStatus
_PyTypes_InitTypes(PyInterpreterState *interp)
{
    for (size_t i=0; i < Py_ARRAY_LENGTH(static_types); i++) {
        PyTypeObject *type = static_types[i];
        if (_PyStaticType_InitBuiltin(interp, type) < 0) {
            return _PyStatus_ERR("Can't initialize builtin type");
        }
        if (type == &PyType_Type) {
            // Sanity checks of the two most important types
            assert(PyBaseObject_Type.tp_base == NULL);
            assert(PyType_Type.tp_base == &PyBaseObject_Type);
        }
    }
    ...
}

Reading this carefully:

  • The array lists &PyBaseObject_Type (object) and &PyType_Type (type) first, with the comment “must be initialized first.” PyType_Ready for type needs object to be a ready type already, because it will set type’s base to object and inherit slots from it — hence the ordering constraint.
  • _PyStaticType_InitBuiltinPyType_Ready is what fills in tp_base, computes tp_mro, and inherits slot functions. For type, this sets PyType_Type.tp_base = &PyBaseObject_Type (the subclass-of link the static initializer left NULL). For object, PyType_Ready leaves tp_base NULL because object genuinely has no base.
  • The two asserts, run immediately after type is readied, verify the bootstrap invariant: object’s base is NULL (root of inheritance) and type’s base is now object. These assertions are the C source’s own statement of the relationship — primary-source confirmation that, once startup completes, type is a subclass of object and object is the rootless base.

The clean summary: the instance-of loop is a compile-time fact (static HEAD_INIT macros); the subclass-of wiring type → object is a runtime-startup fact (PyType_Ready inside _PyTypes_InitTypes). They are different mechanisms completed at different times, and the asserts mark the moment both are true.

Reading the Two Axes — type(), isinstance, issubclass

Once the structure exists, three built-ins read it, and they read different axes — another frequent source of confusion.

type(x) reads the instance-of axis only: it returns x’s ob_type verbatim, with no walking. It answers “what class was x directly created from?” and ignores inheritance entirely. There is also a three-argument form, type(name, bases, dict), which is not a query at all but the class constructor — covered in Metaclasses. CPython distinguishes the two by argument count inside type_call (Objects/typeobject.c), and the one-argument query form is deliberately restricted to type itself, not inherited by metaclass subclasses (a fix for issue gh-27157):

type_call(PyObject *self, PyObject *args, PyObject *kwds)
{
    PyTypeObject *type = PyTypeObject_CAST(self);
    ...
    /* Special case: type(x) should return Py_TYPE(x) */
    /* We only want type itself to accept the one-argument form (#27157) */
    if (type == &PyType_Type) {
        Py_ssize_t nargs = PyTuple_GET_SIZE(args);
        if (nargs == 1 && (kwds == NULL || !PyDict_GET_SIZE(kwds))) {
            obj = (PyObject *) Py_TYPE(PyTuple_GET_ITEM(args, 0));   // return ob_type directly
            return Py_NewRef(obj);
        }
        if (nargs != 3) {
            PyErr_SetString(PyExc_TypeError, "type() takes 1 or 3 arguments");
            return NULL;
        }
    }
    ...
}
  • The guard if (type == &PyType_Type) means the one-arg shortcut fires only when you call type(...) directly, not when you call a custom metaclass MyMeta(x) — for a metaclass, a one-argument call would be a malformed class definition, so it is not silently reinterpreted as a type query.
  • obj = (PyObject *) Py_TYPE(...) is literally “read the ob_type field”; type(x) is that one pointer dereference.

isinstance(x, C) and issubclass(D, C) read the subclass-of axis (with extensions). isinstance(x, C) is, in the common case, equivalent to issubclass(type(x), C): it takes x’s class and asks whether C is in that class’s MRO. issubclass(D, C) asks whether C appears in D.__mro__. The difference from type() is decisive: type(True) is bool (instance-of is exact, and True’s class is bool, not int), but isinstance(True, int) is True because bool inherits from int and so int is in bool.__mro__. Use type(x) is C when you need an exact class match; use isinstance when subclasses should also qualify — the latter is almost always the right default.

isinstance/issubclass can be overridden

isinstance and issubclass are not pure MRO walks: a metaclass may define __instancecheck__/__subclasscheck__ to override them, which is exactly how Abstract Base Classes make register()-ed virtual subclasses and __subclasshook__-matched classes report True without appearing in the MRO. type(), by contrast, can never be overridden this way — it always returns the raw ob_type. This is the deep reason the two are not interchangeable.

Why This Two-Axis Structure Makes Metaclasses Possible

The payoff of the whole arrangement: because “the type of a class” is a real, first-class slot (ob_type) on the instance-of axis — distinct from “the base of a class” on the subclass-of axis — a class object can have a type other than type. That other type is a metaclass: any class whose instances are themselves classes. type is the default and most-derived-common metaclass, but a subclass of type can replace it for a given class. When you write class Foo(Base, metaclass=Meta), you are setting Foo’s instance-of link to Meta while leaving its subclass-of link pointing at Base — the two axes are configured independently, which is only coherent because they are genuinely separate slots.

This is also why type must be its own type and a subclass of object. Being a subclass of object makes type an ordinary class that participates in the data model (it has __repr__, __call__, attribute access). Being an instance of type makes type a class factory — calling it constructs classes. A metaclass Meta(type) inherits both: it is-a class (via object) and it makes classes (via type). Remove either half of the type/object loop and metaclasses collapse — without type being instance-of type, there would be no closed set of “classes that make classes”; without type being subclass-of object, metaclasses could not themselves be manipulated as ordinary objects. The bootstrap loop is not an accident to be tolerated; it is the structural precondition for Python’s uniform “everything is an object, including classes” model.

Common Misunderstandings

object is the base of type, so object must be ‘more fundamental’ — shouldn’t type be an instance of object?” It is: type is both a subclass of object (inheritance) and an instance of type (instance-of). “More fundamental” is ambiguous because the two axes have different roots. object is the root of inheritance; type is the root of instance-of. Neither is globally “first”; they are co-equal and mutually referential, which is exactly why both must be initialized together at the head of static_types[].

type(type) is type is an infinite regress / a bug.” No — it is a fixed point, not a regress. Following the instance-of axis from type lands back on type in one step and stays there; there is nothing to recurse into. The C code expresses this as a static self-reference in PyVarObject_HEAD_INIT(&PyType_Type, 0), fully resolved at compile time.

“Every class inherits from type.” Every class is an instance of type (or a metaclass subclass of type); every class inherits from object. Swapping “instance of” and “inherits from” here is the single most common slip. int does not inherit from typeint.__mro__ is (int, object), with no type in it — yet type(int) is type.

object.__class__ and object.__bases__ should be symmetric.” They sit on different axes. object.__class__ is type (instance-of, non-empty), while object.__bases__ is () (subclass-of, empty). The asymmetry is the whole point: object tops one axis and is an ordinary node on the other.

See Also

  • Metaclasses — the sibling note: the class-creation pipeline, __prepare__/__init_subclass__/__set_name__, conflict resolution, and use cases. Read together with this note.
  • Type Objects and PyTypeObject — the PyTypeObject C struct (the slot table) that both object and type are instances of; defers the bootstrap to here.
  • Method Resolution Order and C3 Linearization — how the subclass-of axis is linearized into __mro__, the axis issubclass walks.
  • Abstract Base ClassesABCMeta overrides __instancecheck__/__subclasscheck__, the mechanism by which isinstance/issubclass diverge from a pure MRO walk.
  • Python Internals MOC §13 — The Type System and Data-Model Protocols.