# 45. The MRO

# 45. The MRO

The method resolution order, usually called the MRO, is the ordered list of classes CPython searches when resolving attributes through a class hierarchy.

For a simple class:

```python
class A:
    pass

class B(A):
    pass
```

the MRO of `B` is:

```python
print(B.__mro__)
```

Output shape:

```text
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
```

This means CPython searches `B`, then `A`, then `object`.

The MRO is central to inheritance, method lookup, descriptors, `super()`, multiple inheritance, abstract base classes, and class layout validation.

## 45.1 Why the MRO Exists

Python supports inheritance. When code asks for an attribute:

```python
obj.name
```

and the instance does not directly provide it, CPython searches the object’s class and base classes.

Without a defined search order, this expression would be ambiguous:

```python
class A:
    def f(self):
        return "A"

class B:
    def f(self):
        return "B"

class C(A, B):
    pass

print(C().f())
```

`C` inherits from both `A` and `B`. Both define `f`.

Python needs a deterministic rule for choosing one.

The MRO gives that rule.

```python
print(C.__mro__)
```

Output shape:

```text
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
```

So `C().f()` returns:

```text
A
```

because `A` appears before `B`.

## 45.2 The MRO Is Stored on the Class

Every class has an MRO.

```python
class User:
    pass

print(User.__mro__)
```

Output shape:

```text
(<class '__main__.User'>, <class 'object'>)
```

The MRO is computed when the class is created and stored on the class object.

It is not recomputed for every attribute access.

You can also call:

```python
print(User.mro())
```

`User.__mro__` returns the stored tuple.

`User.mro()` returns a list-like result computed through class machinery.

For ordinary classes, they contain the same ordering.

## 45.3 Attribute Lookup Uses the MRO

For:

```python
obj.attr
```

normal lookup roughly follows:

```text
1. Look at type(obj).
2. Search type(obj).__mro__ in order.
3. Find attr in a class dictionary.
4. Apply descriptor rules if needed.
5. Fall back to instance dictionary or __getattr__ according to lookup rules.
```

Example:

```python
class A:
    value = "A"

class B(A):
    pass

class C(B):
    pass

c = C()

print(c.value)
```

CPython searches:

```text
C
B
A
object
```

It finds `value` in `A`.

## 45.4 Method Lookup Uses the Same Rule

Methods are attributes stored on classes.

```python
class A:
    def run(self):
        return "A.run"

class B(A):
    pass

b = B()

print(b.run())
```

The method `run` is found in `A`.

Because functions are descriptors, the function object in `A.__dict__` is bound to the instance `b`.

Conceptually:

```text
lookup run in B.__mro__
find A.__dict__["run"]
call function descriptor __get__(b, B)
return bound method
call bound method
```

The MRO decides which function object is found first.

## 45.5 Single Inheritance MRO

Single inheritance is straightforward.

```python
class A:
    pass

class B(A):
    pass

class C(B):
    pass

print(C.__mro__)
```

Output shape:

```text
(C, B, A, object)
```

The search proceeds from most specific to least specific.

```text
child
parent
grandparent
object
```

This is the simple case most code relies on.

## 45.6 Multiple Inheritance MRO

Multiple inheritance requires a more careful algorithm.

```python
class A:
    pass

class B:
    pass

class C(A, B):
    pass

print(C.__mro__)
```

Output shape:

```text
(C, A, B, object)
```

The left-to-right order of base classes matters, but it is not the only rule. Python also preserves ordering constraints from parent classes.

The algorithm used for normal Python classes is C3 linearization.

## 45.7 C3 Linearization

C3 linearization computes a class order that satisfies three important properties:

```text
local precedence order
monotonicity
consistent extension of parent MROs
```

Local precedence order means bases listed earlier in the class definition should remain earlier when possible.

```python
class C(A, B):
    pass
```

means `A` should precede `B`.

Monotonicity means subclassing should not reorder ancestors in a way that contradicts their existing MROs.

Consistent extension means a class MRO includes the MROs of its bases without breaking their internal ordering.

These rules make multiple inheritance predictable.

## 45.8 The C3 Merge Model

For a class:

```python
class C(A, B):
    pass
```

C3 computes:

```text
MRO(C) = [C] + merge(MRO(A), MRO(B), [A, B])
```

The merge operation repeatedly chooses a valid head from the input lists.

A head is valid if it does not appear in the tail of any other list.

This rule prevents choosing a class before another class that must precede it.

## 45.9 Simple C3 Example

```python
class A:
    pass

class B:
    pass

class C(A, B):
    pass
```

Inputs:

```text
MRO(A) = [A, object]
MRO(B) = [B, object]
bases  = [A, B]
```

So:

```text
MRO(C) = [C] + merge(
    [A, object],
    [B, object],
    [A, B]
)
```

Merge:

```text
choose A because A is not in any other tail
choose B because B is not in any other tail
choose object
```

Result:

```text
[C, A, B, object]
```

## 45.10 Diamond Inheritance

The classic multiple inheritance case is the diamond.

```python
class A:
    def f(self):
        return "A"

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.__mro__)
```

Output shape:

```text
(D, B, C, A, object)
```

The base class `A` appears once.

This is important. Python does not produce:

```text
D, B, A, C, A, object
```

A repeated base would make cooperative method calls difficult and ambiguous.

## 45.11 Diamond Method Lookup

```python
class A:
    def f(self):
        return "A"

class B(A):
    pass

class C(A):
    def f(self):
        return "C"

class D(B, C):
    pass

print(D().f())
print(D.__mro__)
```

`D` searches:

```text
D
B
C
A
object
```

`B` does not define `f`.

`C` defines `f`.

So the result is:

```text
C
```

Even though `B` inherits from `A`, Python does not search all of `B` before considering `C`. It searches the linearized MRO.

## 45.12 MRO and `super()`

`super()` follows the MRO.

It does not simply mean “call the parent class.”

Example:

```python
class A:
    def f(self):
        return "A"

class B(A):
    def f(self):
        return "B" + super().f()

class C(A):
    def f(self):
        return "C" + super().f()

class D(B, C):
    def f(self):
        return "D" + super().f()

print(D().f())
print(D.__mro__)
```

Output:

```text
DBCA
```

The MRO is:

```text
D, B, C, A, object
```

So each `super().f()` continues after the current class in that order.

## 45.13 `super()` Is Dynamic

In this class:

```python
class B(A):
    def f(self):
        return "B" + super().f()
```

`super()` does not hard-code `A`.

If `B` is used inside another MRO, `super()` continues after `B` in that MRO.

Example:

```python
class A:
    def f(self):
        return "A"

class B(A):
    def f(self):
        return "B" + super().f()

class C(A):
    def f(self):
        return "C" + super().f()

class D(B, C):
    pass

print(D().f())
```

The call inside `B.f` goes to `C.f`, not directly to `A.f`.

That is the core of cooperative multiple inheritance.

## 45.14 Cooperative Methods

A cooperative method is written so every class in the MRO can participate.

```python
class A:
    def setup(self):
        print("A")
        super().setup()

class B(A):
    def setup(self):
        print("B")
        super().setup()

class C(A):
    def setup(self):
        print("C")
        super().setup()

class D(B, C):
    def setup(self):
        print("D")
        super().setup()
```

This will eventually fail unless the chain ends with a method that accepts the call.

A common root class:

```python
class Root:
    def setup(self):
        pass

class A(Root):
    def setup(self):
        print("A")
        super().setup()
```

Now all participants can call `super().setup()` safely.

## 45.15 Cooperative `__init__`

Multiple inheritance commonly fails when `__init__` methods do not cooperate.

Bad:

```python
class A:
    def __init__(self):
        self.a = 1

class B:
    def __init__(self):
        self.b = 1

class C(A, B):
    pass

c = C()
print(hasattr(c, "a"))
print(hasattr(c, "b"))
```

Only `A.__init__` runs because `C` inherits it first.

Better:

```python
class Root:
    def __init__(self, **kwargs):
        super().__init__()

class A(Root):
    def __init__(self, **kwargs):
        self.a = 1
        super().__init__(**kwargs)

class B(Root):
    def __init__(self, **kwargs):
        self.b = 1
        super().__init__(**kwargs)

class C(A, B):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
```

Now `C().__dict__` contains both `a` and `b`.

## 45.16 Keyword-Based Cooperative Initialization

A common cooperative pattern passes keyword arguments through the MRO.

```python
class Root:
    def __init__(self, **kwargs):
        if kwargs:
            raise TypeError(f"unexpected arguments: {kwargs}")
        super().__init__()

class NameMixin(Root):
    def __init__(self, *, name, **kwargs):
        self.name = name
        super().__init__(**kwargs)

class AgeMixin(Root):
    def __init__(self, *, age, **kwargs):
        self.age = age
        super().__init__(**kwargs)

class User(NameMixin, AgeMixin):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

u = User(name="Ada", age=36)

print(u.name)
print(u.age)
```

Each class consumes the arguments it owns and forwards the rest.

This pattern works because all classes agree to cooperate.

## 45.17 Non-Cooperative Base Classes

Some classes do not call `super()`.

```python
class A:
    def setup(self):
        print("A")
```

If `A` appears before other classes in the MRO, it may stop the chain.

```python
class B:
    def setup(self):
        print("B")
        super().setup()

class C(A, B):
    pass

C().setup()
```

Only `A` may run.

In multiple inheritance, every class in the chain must participate. One non-cooperative class can break the whole method chain.

## 45.18 MRO Conflicts

Some inheritance graphs cannot produce a valid C3 MRO.

Example:

```python
class A:
    pass

class B:
    pass

class X(A, B):
    pass

class Y(B, A):
    pass

class Z(X, Y):
    pass
```

This fails.

`X` requires:

```text
A before B
```

`Y` requires:

```text
B before A
```

`Z` cannot satisfy both.

CPython raises:

```text
TypeError: Cannot create a consistent method resolution order
```

This is a design error in the class hierarchy.

## 45.19 Local Precedence Order

Local precedence order means the order of bases in a class definition matters.

```python
class C(A, B):
    pass
```

states that `A` should precede `B`.

Changing the base order changes the MRO.

```python
class C1(A, B):
    pass

class C2(B, A):
    pass

print(C1.__mro__)
print(C2.__mro__)
```

Base order is therefore part of the class API.

## 45.20 Monotonicity

Monotonicity means subclassing a class should not reorder that class’s ancestors.

If `A` precedes `B` in a parent’s MRO, a subclass should not silently reverse them.

This property makes subclass behavior predictable. Adding a subclass should not change the meaning of a parent class’s method resolution assumptions.

C3 enforces this.

## 45.21 `object` at the End

For ordinary new-style classes, `object` appears at the end of the MRO.

```python
class User:
    pass

print(User.__mro__)
```

Output shape:

```text
(User, object)
```

`object` provides fundamental behavior such as:

```text
__new__
__init__
__repr__
__str__
__eq__
__hash__
__getattribute__
__setattr__
```

Even a class with no explicit base inherits from `object`.

## 45.22 MRO and Descriptors

Descriptors are found through MRO lookup.

```python
class A:
    @property
    def value(self):
        return "A"

class B(A):
    pass

print(B().value)
```

The property descriptor is found in `A.__dict__`.

Then descriptor logic calls:

```text
A.__dict__["value"].__get__(instance, B)
```

The owner type passed to the descriptor is usually the actual class involved in access, such as `B`.

This matters for descriptors that inspect `objtype`.

## 45.23 Data Descriptor Precedence and MRO

If a data descriptor appears in a base class, it can override an instance dictionary entry.

```python
class A:
    @property
    def value(self):
        return "from property"

class B(A):
    pass

b = B()
b.__dict__["value"] = "from dict"

print(b.value)
```

Output:

```text
from property
```

The descriptor is found through the MRO, and because it is a data descriptor, it wins over the instance dictionary.

## 45.24 Non-Data Descriptor and MRO

Methods are non-data descriptors.

```python
class A:
    def f(self):
        return "method"

class B(A):
    pass

b = B()
b.__dict__["f"] = lambda: "instance"

print(b.f())
```

Output:

```text
instance
```

The instance dictionary can shadow the method because function descriptors are non-data descriptors.

The MRO finds the method, but descriptor precedence still allows the instance dictionary to win.

## 45.25 MRO and Class Attribute Lookup

For class attribute lookup:

```python
B.attr
```

CPython searches `B.__mro__`.

```python
class A:
    x = 1

class B(A):
    pass

print(B.x)
```

The attribute `x` is found in `A`.

Class lookup also interacts with metaclass lookup. If the class itself does not provide the attribute, the metaclass may provide descriptors or methods.

## 45.26 MRO and Metaclasses

The class object has its own type, the metaclass.

```python
class Meta(type):
    def describe(cls):
        return cls.__name__

class User(metaclass=Meta):
    pass

print(User.describe())
```

`describe` is found on `Meta`, not in `User.__mro__`.

There are two related lookup structures:

```text
instance attribute lookup:
    instance -> class MRO

class object attribute lookup:
    class object -> class MRO and metaclass machinery
```

Metaclasses have their own MRO too:

```python
print(type(User).__mro__)
```

## 45.27 MRO and Abstract Base Classes

Abstract base classes participate in normal inheritance.

```python
from abc import ABC, abstractmethod

class Store(ABC):
    @abstractmethod
    def get(self, key):
        pass

class MemoryStore(Store):
    def get(self, key):
        return None

print(MemoryStore.__mro__)
```

Output shape:

```text
(MemoryStore, Store, ABC, object)
```

The abstract method machinery uses metaclass behavior, but method lookup still follows the MRO.

## 45.28 Virtual Subclasses

ABCs can register virtual subclasses.

```python
from abc import ABC

class Plugin:
    pass

class PluginABC(ABC):
    pass

PluginABC.register(Plugin)

print(issubclass(Plugin, PluginABC))
print(PluginABC in Plugin.__mro__)
```

Output:

```text
True
False
```

Virtual subclassing affects `issubclass` and `isinstance`, but it does not insert the ABC into the concrete class’s MRO.

Therefore, virtual base methods are not found by ordinary attribute lookup.

## 45.29 MRO and Mixins

A mixin is a class designed to be combined with other classes.

```python
class JsonMixin:
    def to_json(self):
        import json
        return json.dumps(self.to_dict())
```

A mixin usually expects the final class to provide some methods:

```python
class User(JsonMixin):
    def to_dict(self):
        return {"name": "Ada"}
```

Mixins should be small, cooperative, and explicit about expectations.

Good mixins:

```text
define narrow behavior
avoid heavy __init__
call super when overriding cooperative methods
avoid owning unrelated state
document required methods
```

## 45.30 Mixin Ordering

Mixin order matters.

```python
class LoggingMixin:
    def save(self):
        print("log")
        return super().save()

class Store:
    def save(self):
        print("store")

class Model(LoggingMixin, Store):
    pass

Model().save()
```

Output:

```text
log
store
```

If the order is reversed:

```python
class Model(Store, LoggingMixin):
    pass
```

then `Store.save` may run first and stop the chain.

Base order should be chosen deliberately.

## 45.31 MRO and Frameworks

Frameworks often rely on MRO behavior.

Examples:

```text
ORM model classes
class-based web views
serializer classes
test case classes
form classes
plugin base classes
dataclass-like transforms
```

A class-based view might combine:

```python
class View:
    def dispatch(self):
        ...

class AuthMixin:
    def dispatch(self):
        check_auth()
        return super().dispatch()

class LoggingMixin:
    def dispatch(self):
        log_request()
        return super().dispatch()

class UserView(AuthMixin, LoggingMixin, View):
    pass
```

The MRO defines the request handling chain.

## 45.32 Inspecting the MRO

Use `__mro__`:

```python
print(UserView.__mro__)
```

Use `mro()`:

```python
print(UserView.mro())
```

Use `inspect.getmro`:

```python
import inspect

print(inspect.getmro(UserView))
```

When debugging inheritance, always inspect the actual MRO rather than guessing from the class diagram.

## 45.33 Tracing Method Resolution

To see where a method comes from:

```python
def where_defined(cls, name):
    for base in cls.__mro__:
        if name in base.__dict__:
            return base
    return None
```

Example:

```python
print(where_defined(UserView, "dispatch"))
```

This reports the first class in the MRO that defines the attribute.

For descriptor-aware behavior, this only finds the raw defining class. Actual access may still involve descriptor binding.

## 45.34 MRO and `__bases__`

A class stores direct bases in `__bases__`.

```python
class A:
    pass

class B(A):
    pass

print(B.__bases__)
print(B.__mro__)
```

`__bases__` gives immediate parents.

`__mro__` gives the full linearized search order.

These are related but not the same.

## 45.35 Modifying `__bases__`

Python allows changing `__bases__` in some cases.

```python
class A:
    pass

class B:
    pass

class C(A):
    pass

C.__bases__ = (B,)
```

This asks CPython to recompute the MRO and validate layout compatibility.

It may fail if the new bases are incompatible.

Dynamic base changes are rare and should be avoided in normal code. They can invalidate assumptions about layout, methods, and type checks.

## 45.36 Layout Conflicts

Multiple inheritance is constrained by instance layout.

Some built-in types cannot be combined freely:

```python
class Bad(dict, list):
    pass
```

This fails because `dict` and `list` have incompatible C-level memory layouts.

CPython must ensure that instances have a coherent object layout.

The MRO is about method order, but class creation must also validate memory layout and slot compatibility.

## 45.37 MRO and `__slots__`

Slots affect instance layout.

```python
class A:
    __slots__ = ("a",)

class B:
    __slots__ = ("b",)

class C(A, B):
    pass
```

Multiple inheritance with slots can fail depending on layout compatibility.

Only one base class with a non-empty instance layout can usually contribute certain low-level layout features.

This is a CPython-level constraint, not merely a method lookup rule.

## 45.38 MRO and Special Methods

Special methods are resolved on the type, usually through slots.

Example:

```python
class A:
    def __len__(self):
        return 1

class B(A):
    pass

print(len(B()))
```

CPython finds the special method through class machinery.

But assigning `__len__` to an instance does not affect `len(obj)`:

```python
b = B()
b.__len__ = lambda: 100

print(len(b))
```

Still:

```text
1
```

Special method lookup depends on the class and its MRO, not ordinary instance attribute lookup.

## 45.39 MRO and Operator Overloading

Operators use special methods found through type lookup.

```python
class A:
    def __add__(self, other):
        return "A add"

class B(A):
    pass

print(B() + B())
```

The implementation is found through the class hierarchy.

If a subclass overrides `__add__`, it wins:

```python
class C(A):
    def __add__(self, other):
        return "C add"

print(C() + C())
```

The MRO controls which special method is selected, subject to binary operator dispatch rules.

## 45.40 MRO and Binary Operators

Binary operator lookup has extra rules for subclasses.

For:

```python
a + b
```

CPython considers methods such as:

```text
type(a).__add__
type(b).__radd__
```

If the right operand’s type is a subclass of the left operand’s type, CPython may give the right operand’s reflected method priority.

This is still type-based lookup, but it is more complex than a simple MRO search on one class.

The key point: operator dispatch uses class-level special methods, not instance attributes.

## 45.41 MRO and `super(type, obj)`

The explicit form of `super` is:

```python
super(CurrentClass, obj)
```

This creates a proxy that starts searching after `CurrentClass` in `type(obj).__mro__`.

Example:

```python
class A:
    def f(self):
        return "A"

class B(A):
    def f(self):
        return "B" + super(B, self).f()
```

Inside `B.f`, this searches after `B`.

Zero-argument `super()` is compiler-assisted. CPython stores enough context to identify the current class and first argument.

## 45.42 Zero-Argument `super`

This form:

```python
super()
```

works inside normal methods because the compiler creates a hidden `__class__` cell when needed.

Example:

```python
class B(A):
    def f(self):
        return super().f()
```

The call is roughly equivalent to:

```python
super(__class__, self).f()
```

The hidden `__class__` cell is part of class creation and function closure handling.

This is one reason `super()` has special compiler support.

## 45.43 MRO and `__class__` Cell

When a method references `__class__` or uses zero-argument `super()`, the compiler creates a `__class__` closure cell.

```python
class A:
    def f(self):
        return __class__
```

This cell is filled with the created class object after class creation.

Metaclasses that manipulate class namespaces must preserve this cell correctly, or class creation can fail.

This connects the compiler, class creation, closures, and MRO behavior.

## 45.44 Customizing MRO With Metaclasses

A metaclass can customize MRO computation by defining `mro`.

```python
class Meta(type):
    def mro(cls):
        return super().mro()

class A(metaclass=Meta):
    pass
```

This is rare.

Changing MRO behavior can break assumptions in Python’s object model. It can affect descriptors, `super`, special methods, and framework behavior.

Most metaclasses should not override `mro`.

## 45.45 MRO Entries and Generic Aliases

Some objects in base-class position can replace themselves using `__mro_entries__`.

Example shape:

```python
class Alias:
    def __mro_entries__(self, bases):
        return (RealBase,)

class C(Alias()):
    pass
```

The MRO is computed after base substitution.

This hook is important for typing-related constructs and generic aliases.

It allows syntax in base positions to behave differently from direct inheritance.

## 45.46 MRO and Generic Classes

Modern Python typing can make class bases look complex:

```python
from typing import Generic, TypeVar

T = TypeVar("T")

class Box(Generic[T]):
    pass
```

At runtime, the resulting class still has an MRO.

```python
print(Box.__mro__)
```

Typing machinery may use `__mro_entries__`, metadata attributes, and special base classes, but ordinary method lookup still relies on the class MRO.

## 45.47 MRO and Protocols

Protocols from `typing` define structural interfaces for type checkers.

```python
from typing import Protocol

class SizedLike(Protocol):
    def __len__(self) -> int:
        ...
```

A class does not need `SizedLike` in its MRO for a static type checker to consider it compatible.

Runtime method lookup remains nominal and MRO-based.

Static structural typing and runtime MRO lookup are different systems.

## 45.48 Practical MRO Debugging

When a method call surprises you, ask these questions:

```text
What is type(obj)?
What is type(obj).__mro__?
Which class first defines the attribute?
Is the attribute a data descriptor?
Is an instance dictionary value shadowing it?
Does the method call super?
Does every class in the chain cooperate?
Is a class imported twice under different names?
Is a metaclass involved?
```

Use code:

```python
def explain_lookup(obj, name):
    cls = type(obj)

    print("type:", cls)
    print("mro:", cls.__mro__)

    for base in cls.__mro__:
        if name in base.__dict__:
            print("found in:", base)
            print("raw value:", base.__dict__[name])
            break
    else:
        print("not found in class MRO")

    if hasattr(obj, "__dict__"):
        print("instance dict:", obj.__dict__)
```

This gives a concrete view of lookup.

## 45.49 Design Rules for Multiple Inheritance

Use multiple inheritance when the classes are designed to cooperate.

Prefer mixins that are narrow and stateless.

Keep base order deliberate.

Call `super()` in cooperative methods.

Use compatible method signatures.

Avoid unrelated concrete base classes with independent state.

Avoid mixing classes with incompatible C-level layouts.

Inspect `__mro__` when behavior is unclear.

Do not use multiple inheritance as a substitute for composition when the relationship is not truly type-level behavior.

## 45.50 Key Points

The MRO is the linear class search order used by inheritance.

CPython stores the MRO on each class as `__mro__`.

Python uses C3 linearization for normal multiple inheritance.

The MRO preserves local base order and parent ordering constraints.

Attribute lookup, method lookup, descriptors, `super()`, and many special methods depend on the MRO.

`super()` continues after the current class in the runtime MRO.

Diamond inheritance works because shared ancestors appear once.

Some inheritance graphs are invalid because no consistent MRO exists.

Virtual ABC registration affects `isinstance` and `issubclass`, but it does not insert classes into the MRO.

Good multiple inheritance requires cooperative classes, compatible signatures, and deliberate base ordering.
