Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Python's builtin type.__init__(name,bases,dct) do anything?

I've seen some examples of Python metaclasses use a super() call to type.__init__(). What does this do?

Example:

class Meta(type):
    def __new__(cls, name, bases, dct):
        dct['a']='a'
        cls_obj = super(Meta, cls).__new__(cls, name, bases, dct)
        return cls_obj

    def __init__(cls_obj, name, bases, dct):
        cls_obj.b = 'b'
        dct['c'] = 'c'
        #what does this do
        super(Meta, cls_obj).__init__(name, bases, dct)

class Meta2(Meta):
    def __init__(cls_obj, name, bases, dct):
        cls_obj.b = 'b'

class Klass(metaclass=Meta):
    pass

class Klass2(metaclass=Meta2):
    pass


if __name__ == '__main__':
    print(Klass.a)
    print(Klass.b)
    print(Klass.c)

Output:

a
b
<...my system traceback...>
AttributeError: type object 'Klass' has no attribute 'c'

Clearly dct is not used to update Klass.__dict__. As far as I can tell, this doesn't do anything. Does it do something? Is there a reason you might want to include it? Is there any effective difference between Klass and Klass2?

Note: I'm talking specifically about the case when Meta inherits from type, not some custom superclass.

like image 756
T.A. Avatar asked Apr 26 '26 01:04

T.A.


1 Answers

The type.__init__() implementation does indeed do nothing, other than validate the argument count and calling object.__init__(cls) (which again does nothing but a few sanity checks).

However, for metaclasses that are inherited from and have to account for other mixin metaclasses, using super().__init__(name, bases, namespace) ensures that all metaclasses in the MRO are consulted.

E.g. when using multiple metaclasses to base a new metaclass on, what is called via super().__init__() changes:

>>> class MetaFoo(type):
...     def __init__(cls, name, bases, namespace):
...         print(f"MetaFoo.__init__({cls!r}, {name!r}, {bases!r}, {namespace!r})")
...         super().__init__(name, bases, namespace)
...
>>> class MetaMixin(type):
...     def __init__(cls, name, bases, namespace):
...         print(f"MetaMixin.__init__({cls!r}, {name!r}, {bases!r}, {namespace!r})")
...         super().__init__(name, bases, namespace)
...
>>> class MetaBar(MetaFoo, MetaMixin):
...     def __init__(cls, name, bases, namespace):
...         print(f"MetaBar.__init__({cls!r}, {name!r}, {bases!r}, {namespace!r})")
...         super().__init__(name, bases, namespace)
...
>>> class Foo(metaclass=MetaFoo): pass
...
MetaFoo.__init__(<class '__main__.Foo'>, 'Foo', (), {'__module__': '__main__', '__qualname__': 'Foo'})
>>> class Bar(metaclass=MetaBar): pass
...
MetaBar.__init__(<class '__main__.Bar'>, 'Bar', (), {'__module__': '__main__', '__qualname__': 'Bar'})
MetaFoo.__init__(<class '__main__.Bar'>, 'Bar', (), {'__module__': '__main__', '__qualname__': 'Bar'})
MetaMixin.__init__(<class '__main__.Bar'>, 'Bar', (), {'__module__': '__main__', '__qualname__': 'Bar'})

Note how MetaMixin() is called last (before type.__init__() is called). If MetaMixin.__init__() were to consult the namespace dictionary for its own uses, then altering namespace in MetaFoo.__init__() would alter what MetaMixin.__init__() finds in that dictionary.

So for cases where you see super() being used in the __init__ method of a metaclass, you may want to check for more complex metaclass hierarchies. Or the project is just playing it safe and making sure that their metaclasses can be inherited from in more complex scenarios.

The namespace dictionary argument (you used the name dct) is copied before using it as the class attribute dictionary, so adding new keys to it in __init__ will not, indeed, alter the class dictionary.

like image 164
Martijn Pieters Avatar answered Apr 28 '26 15:04

Martijn Pieters