Search code examples
pythonpython-3.xinheritanceinitmetaclass

Python metaclass __init__ method gets called when creating child classes


So I'm learning how python metaclass works. I have the following define a metaclass:

class XMeta(type):
    def __init__(cls, name, bases, dct):
        print('yrdy!')
        super().__init__(name, bases, dct)

Then creating a real class

X = XMeta('X', (), {})

What confuses me is when inheritance comes in:

class Y(X):
    pass

Then the print statements gets called, yrdy! gets printed. When you trying to create another child class:

class Z(X):
    pass

yrdy! gets printed again.

So my question is, if we consider X as an instance, since X is already gets defined and initialized, when creating class Z, why would Xmeta.__init__ gets called again?


Solution

  • Metaclasses __new__ and __init__ methods will be called for every class that uses them, and all classes in Python will always use the most derived metaclass in its superclasses.

    That means, most of the time, any metaclass inserted in an inheritance chain will be used to create a child class.

    That differs from the behavior, for example, of a class decorator (like @dataclass), which only affects the decorated class.

    Continuing on your text "X" is an instance, yes, but an instance of the metaclass. When you inherit from it on Y, it is not "consumed", it is used as one of the base classes for the new class you are creating. The language spec determines them that its metaclass will be used when creating Y. Do not mistake inheting from "X" with creating new instances of "X", which is what happen when you call X(): in this case there is no __init__ method in the class itself, and nothing will be run.

    Note that even if you'd create Y deriving from X programatically by calling the 3 argument form of type, since the rule is to use the most derived metaclass from all the bases, XMeta would still be used (and its __init__ method run):

    In [11]: class XMeta(type):
        ...:     def __init__(cls, name, bases, dct):
        ...:         print('yrdy!')
        ...:         super().__init__(name, bases, dct)
        ...: 
    
    In [12]: X = XMeta('X', (), {})
    yrdy!
    
    In [13]: Y = type('Y', (X,), {})
    yrdy!