PythonProgrammingSenior Python Developer

Through what dynamic substitution mechanism does a **Python** non-class object specify its participating classes when appearing in a class inheritance list, thereby influencing Method Resolution Order calculation?

Pass interviews with Hintsage AI assistant

Answer to the question

History of the question

With the adoption of PEP 560 in Python 3.7, the type system required a way to use generic types such as List[int] or Generic[T] as base classes. Prior to this enhancement, attempting to inherit from a parameterized generic resulted in a TypeError because these objects were not actual classes, forcing developers to resort to complex metaclass workarounds that complicated library design.

The problem

When the interpreter processes a class definition, it must compute the Method Resolution Order (MRO) using the C3 linearization algorithm. This algorithm requires all bases to be classes. The challenge arises when a base object is not a class but a generic alias; the interpreter needs a protocol to determine which real classes should stand in for this alias during MRO construction without breaking the inheritance semantics.

The solution

Python introduced the __mro_entries__ protocol. When class creation encounters a base with this method, it calls base.__mro_entries__(original_bases) and expects a tuple of classes in return. These classes replace the original base in the MRO calculation. For instance, typing.Generic implements this to return (Generic,), allowing it to function as a base while the parameterized logic remains separate.

from typing import Generic, TypeVar T = TypeVar('T') # Generic[T] is not a class, but __mro_entries__ allows it to act as one class Container(Generic[T]): pass # Container.__mro__ includes Generic, not Generic[T] print(Container.__mro__) # (<class 'Container'>, <class 'typing.Generic'>, <class 'object'>)

Situation from life

A framework team needed to allow users to define data models using parameterized generic bases like Model[UserType]. Their initial approach used a custom metaclass to intercept the class creation and extract type parameters, but this forced users to resolve metaclass conflicts manually when combining the framework with Django or SQLAlchemy models.

They considered using a class decorator to rewrite the class after definition, but this approach broke static type checking and IDE autocompletion because the transformation occurred after the type checker analyzed the source code. Another alternative involved __init_subclass__, but this could not handle the case where the base itself was not a class.

The team implemented __mro_entries__ on their generic factory objects. When users wrote class UserModel(Model[UserType]), the Model[UserType] instance returned (Model,) from its __mro_entries__ method. This allowed the class to inherit correctly from Model while the factory stored the specific type parameter for runtime validation. The solution eliminated metaclass conflicts, preserved full IDE support, and maintained a clean inheritance hierarchy that satisfied the C3 linearization algorithm.

What candidates often miss

Does __mro_entries__ affect runtime type checking or isinstance behavior?

Candidates often confuse MRO construction with instance checking. __mro_entries__ operates exclusively during class creation to build the __mro__ tuple. It has no effect on isinstance() or issubclass() checks at runtime. Those operations rely on the __class__ and __bases__ attributes of existing classes, not the dynamic substitution that occurred during the class definition phase.

Why does __mro_entries__ return a tuple rather than a single class?

The tuple return type accommodates complex multiple inheritance scenarios. Although commonly returning a single-element tuple like (Generic,), the protocol allows a generic parameter to imply inheritance from multiple mixins simultaneously. Python unpacks this tuple directly into the bases list for MRO calculation, so returning (A, B) effectively makes the class inherit from both A and B instead of the original non-class base.

What validation does Python perform on the classes returned by __mro_entries__?

The interpreter strictly validates that the returned classes form a valid inheritance graph. If the tuple contains classes that would create an inconsistent MRO—such as introducing a diamond inheritance conflict that violates the C3 linearization constraints—Python raises a TypeError during class creation. This validation ensures that dynamic substitution cannot circumvent the language's fundamental inheritance consistency rules.