When dealing with metaclass inheritance, Python follows a specific and often non-intuitive resolution order to determine which metaclass to use for a new class. This process is crucial to understand because a metaclass conflict can prevent a class from being created entirely. The resolution algorithm is designed to ensure consistency; a class cannot have two different metaclasses that are not in a subclass relationship, as this would create an ambiguous situation for how the class should be constructed.

The Metaclass Resolution Algorithm

Python’s algorithm for metaclass determination is defined in the language’s C implementation but can be logically understood. When the class statement is executed, Python follows these steps:

  1. If no explicit metaclass is defined for the class being created, it checks the base classes.
  2. If an explicit metaclass is found (via the metaclass keyword argument), it is used.
  3. If no explicit metaclass is given, but there are base classes, Python collects the metaclasses of all the base classes.
  4. If there are no metaclasses collected from the bases, the default metaclass, type, is used.
  5. If exactly one distinct metaclass is found among the bases, that metaclass is used.
  6. If multiple distinct metaclasses are found, Python checks if they are all subclasses of a common metaclass. If so, the most derived (most specific) metaclass is used. This is the critical step for avoiding conflicts.
  7. If no common, most derived metaclass can be found, a TypeError is raised due to a metaclass conflict.

This process ensures that the resulting metaclass is the most specific one that is a subtype of all the candidate metaclasses from the base classes.

class MetaA(type):
    pass

class MetaB(type):
    pass

class A(metaclass=MetaA):
    pass

class B(metaclass=MetaB):
    pass

# This will raise a TypeError because MetaA and MetaB are unrelated,
# and Python cannot choose a single, consistent metaclass.
try:
    class Conflict(A, B):
        pass
except TypeError as e:
    print(f"Metaclass conflict: {e}")

Resolving Conflicts with a Derived Metaclass

The correct way to resolve a metaclass conflict is to explicitly define a new metaclass that inherits from all the metaclasses of the base classes. This new metaclass becomes the “most derived” metaclass that Python’s algorithm will select. It acts as a union of the parent metaclasses, and its Method Resolution Order (MRO) determines the order in which metaclass methods are called.

class MetaA(type):
    def __init__(cls, name, bases, dct):
        print(f"MetaA.__init__ called for {name}")
        super().__init__(name, bases, dct)

class MetaB(type):
    def __init__(cls, name, bases, dct):
        print(f"MetaB.__init__ called for {name}")
        super().__init__(name, bases, dct)

# Create a new metaclass that derives from both MetaA and MetaB.
# The order of inheritance matters for the MRO.
class MetaAB(MetaA, MetaB):
    def __init__(cls, name, bases, dct):
        print(f"MetaAB.__init__ called for {name}")
        # super() will follow the MRO, calling MetaA.__init__ first, then MetaB.__init__.
        super().__init__(name, bases, dct)

class A(metaclass=MetaA):
    pass

class B(metaclass=MetaB):
    pass

# Now this works. Python sees that the bases have MetaA and MetaB,
# but finds that MetaAB is a subclass of both, so it uses MetaAB.
class C(A, B, metaclass=MetaAB):
    pass

# Output (order determined by MetaAB's MRO):
# MetaAB.__init__ called for C
# MetaA.__init__ called for C
# MetaB.__init__ called for C

The Role of __build_class__ and __prepare__

The metaclass resolution happens during the execution of the class statement, which internally uses the type.__build_class__ function. This function is responsible for coordinating the entire class creation process, including determining the appropriate metaclass and calling its __new__ and __init__ methods. The __prepare__ method of the chosen metaclass is called first to create the namespace object (e.g., an OrderedDict) that will collect the class attributes during the body’s execution. A conflict prevents __build_class__ from even reaching the point of calling __prepare__.

Common Pitfalls and Best Practices

  • Implicit Metaclass Inheritance: Forgetting that a base class has a metaclass is a common source of unexpected conflicts. Always be aware of the metaclasses in your inheritance hierarchy, especially when using third-party libraries like ORMs (e.g., Django models) or frameworks.
  • Mixing with abc.ABC: The abc.ABCMeta metaclass is frequently involved in conflicts. If you are creating an abstract base class with a custom metaclass, your metaclass must inherit from ABCMeta (or vice versa) to avoid a conflict with other classes that are also ABCs.
  • Explicit is Better Than Implicit: The safest practice is to always explicitly declare the metaclass for a class, especially when building complex frameworks. If you are creating a class that inherits from multiple bases with metaclasses, proactively defining a compatible derived metaclass is mandatory.
  • Testing the Resolution: If you are unsure which metaclass will be chosen, you can check the __class__ attribute of the class (not an instance) or use type(the_class).
class MyMeta(type):
    pass

class Base(metaclass=MyMeta):
    pass

# The metaclass is inherited from Base, even if not explicitly stated.
class Derived(Base):
    pass

print(type(Derived)) # Output: <class '__main__.MyMeta'>
print(Derived.__class__) # Output: <class '__main__.MyMeta'>