How is the metaclass of a subclass determined?

Viewed 41

For the code below:

class TestMeta(type):
    def __init__(cls, classname, bases, dict_):
        print(f'In TestMeta, class {cls}')
        type.__init__(cls, classname, bases, dict_)

class Parent(metaclass=TestMeta):
    pass

class Child(Parent, metaclass=type):
    pass

The output is:

In TestMeta, class <class '__main__.Parent'>
In TestMeta, class <class '__main__.Child'>

In my opinion, when creating the class Parent, TestMeta.__init__ will run, but why it runs again when creating the class Child which I have changed its metaclass as type? How is the metaclass determined when inheriting and passing a metaclass?

1 Answers

From the docs:

The appropriate metaclass for a class definition is determined as follows:

  • if no bases and no explicit metaclass are given, then type() is used;

  • if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass;

  • if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used.

The most derived metaclass is selected from the explicitly specified metaclass (if any) and the metaclasses (i.e. type(cls)) of all specified base classes. The most derived metaclass is one which is a subtype of all of these candidate metaclasses.

In your case above, the third bullet holds, as type is given as the explicit metaclass and there are base-classes.

According to the definition of the most derived metaclass, the candidates are: TestMeta and type. Since TestMeta inherits type, it is the most derived metaclass and is indeed the metaclass of Child.

To demonstrate the difference, the following code shows how we can "force" Child to get the specified metaclass instead of the inherited one:

class TestMeta(type):
    def __init__(cls, classname, bases, dict_):
        print(f'In TestMeta, class {cls}')
        type.__init__(cls, classname, bases, dict_)

class OtherMeta(TestMeta):
    def __init__(cls, classname, bases, dict_):
        print(f'In OtherMeta, class {cls}')
        type.__init__(cls, classname, bases, dict_)

class Parent(metaclass=TestMeta):
    pass

class Child(Parent, metaclass=OtherMeta):
    pass

The output would be:

In TestMeta, class <class '__main__.Parent'>
In OtherMeta, class <class '__main__.Child'>

Notice that again, according to the definition of the most derived metaclass, this only works if OtherMeta itself is a subclass of TestMeta. Removing this inheritance relation will give the error:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Related