History of the question
The __mro_entries__ protocol was introduced in Python 3.7 via PEP 560 ("Core support for typing module and generic types"). Prior to this enhancement, generic aliases such as typing.List[int] could not be used as base classes in class definitions because type.__new__ strictly required all bases to be instances of type. This limitation forced the typing module to rely on fragile metaclass hacks that were difficult to maintain and caused performance issues. The protocol was designed to decouple the syntactic expression of a base from its semantic contribution to the inheritance graph, enabling cleaner support for generics and factory patterns.
The problem
When CPython processes a class definition, it must compute the Method Resolution Order (MRO) using the C3 linearization algorithm to ensure a consistent and predictable method lookup hierarchy. If a base object is not a class (for example, a parameterized generic or a configuration object), the interpreter lacks the necessary type information to place the new class correctly within the inheritance tree. Simply ignoring such objects would break isinstance checks and super() chains, while rejecting them outright would prevent powerful metaprogramming patterns. The core challenge was to allow these non-class objects to declare which concrete classes they logically represent during the class construction phase.
The solution
Python now inspects each item in the bases tuple for a __mro_entries__(self, bases) method during class creation. If this method exists, it is invoked with the original bases tuple, and it must return a tuple of actual classes to substitute for the object in the MRO calculation. The returned classes are then treated as if they had been explicitly listed as bases. This mechanism allows an instance to act as a transparent placeholder that resolves to concrete classes at definition time.
class ConfigurableMixin: def __init__(self, feature): self.feature = feature def __mro_entries__(self, bases): # Dynamically inject base classes based on configuration if self.feature == "logging": return (LoggingSupport,) return (BaseFeature,) class LoggingSupport: def log(self, msg): print(msg) class BaseFeature: pass # The instance is replaced by LoggingSupport in the MRO class Service(ConfigurableMixin("logging")): pass print(LoggingSupport in Service.__mro__) # True
In a large asynchronous web framework, developers needed to create a DatabaseMixin factory that, when instantiated with a specific database URL (e.g., DatabaseMixin("postgresql://")), would automatically inject both ConnectionPool and AsyncSession as base classes into the user's service class. The difficulty was that DatabaseMixin(...) returned a plain object instance, not a class, yet it needed to participate in the MRO as if the developer had explicitly written class UserService(ConnectionPool, AsyncSession).
Solution 1: Custom Metaclass
One approach involved creating a metaclass that scanned the bases tuple in __new__, identified DatabaseMixin instances, and replaced them with the target classes before calling super().__new__. This allowed precise control but introduced the "metaclass conflict" problem: any service using this metaclass could not inherit from other classes that defined their own metaclasses, such as certain ORM base classes. Additionally, debugging became difficult because the class definition syntax hid complex transformations, and stack traces pointed to metaclass internals rather than user code.
Solution 2: Post-Creation Class Decoration
Another option was to use a class decorator applied after the class was created. The decorator would manually copy methods from ConnectionPool and AsyncSession onto the new class or use type.__setattr__ to inject them. While this avoided metaclass virality, it fundamentally broke Python's inheritance model: isinstance(UserService(), ConnectionPool) would return False, and super() calls within the copied methods would resolve incorrectly because the MRO did not actually contain the parent classes. This led to subtle bugs where framework utilities failed to recognize services as database-capable.
Solution 3: __mro_entries__ Protocol
The team chose to implement __mro_entries__ on the object returned by DatabaseMixin. The method returned (ConnectionPool, AsyncSession) based on the parsed URL. This solution integrated seamlessly with CPython's native class creation machinery. The MRO was calculated correctly, isinstance checks worked naturally, and there were no metaclass conflicts. The factory instance acted as a declarative placeholder that dissolved into the proper inheritance structure during class construction, preserving super() semantics and compatibility with multiple inheritance.
The result was a clean, intuitive API where developers could write class OrderService(DatabaseMixin(postgres_url)): and automatically receive connection pooling and session management capabilities with correct method resolution, full IDE support, and zero runtime overhead or inheritance conflicts.
How does C3 linearization handle potential duplicates when __mro_entries__ expands a base into classes already present elsewhere in the inheritance list?
When __mro_entries__ returns a class that also appears elsewhere in the bases (for example, if one factory expands to (BaseA,) and another explicit base is Derived(BaseA)), Python's C3 algorithm treats the expanded tuple as the effective base list. The algorithm then merges these lists while preserving local precedence order and ensuring monotonicity. Because C3 is designed to handle common ancestors, BaseA appears only once in the final MRO, positioned after all classes that depend on it but before object. Candidates often mistakenly believe this creates a conflict or duplicate entry, but the linearization process naturally deduplicates while maintaining the "children before parents" constraint, ensuring consistent method resolution.
Why can __mro_entries__ not access the class being created, and what specific error occurs if it attempts to do so?
During class creation, type.__new__ calls __mro_entries__ on the base objects before the class object itself is instantiated. The namespace dictionary exists, but the class object does not yet have an identity. If the implementation attempts to access attributes of the prospective class (for instance, by referencing the class name from an outer scope or attempting to inspect bases as if they were already bound to the new class), it will raise a NameError or AttributeError because the binding does not yet exist. Candidates frequently assume they can inspect the class's final state or __dict__ to make dynamic decisions, but the method only receives the tuple of original bases as an argument and must rely on its own internal state to determine the return value.
Does registering an object with __mro_entries__ as a virtual subclass of an ABC via abc.ABCMeta.register() cause the ABC to appear in the MRO?
No. Virtual subclass registration is a runtime mechanism that populates an internal cache within the ABC for isinstance() and issubclass() checks. It does not alter the __mro__ attribute of the subclass. When MyClass(MyObject()) is defined and MyObject() returns (ConcreteBase,) via __mro_entries__, only ConcreteBase appears in MyClass.__mro__. If ConcreteBase is registered as a virtual subclass of MyABC, then isinstance(MyClass(), MyABC) returns True, but MyABC will not be present in MyClass.__mro__. Candidates often conflate virtual subclassing with true inheritance, leading to confusion about why super() calls or MRO inspection do not reflect the ABC relationship, or why methods defined on the ABC are not available via inheritance.