Reputation: 118
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.
Upvotes: 2
Views: 524
Reputation: 1121744
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.
Upvotes: 6
Reputation: 50076
The type.__init__
only ensures that:
object.__init__
is called, which
None of name
, bases
, or dct
are actually used by either __init__
method. There are no side-effects beyond what the initial call already did (e.g. checking the validity of arguments).
As a result, skipping the call to super().__init__(name, bases, dct)
does not have any negative side-effects if the superclass is type
. However, similar to classes inheriting from object
, calling super().__init__
is the proper design to stay future-proof - both for changes to the inheritance hierarchy and type
.
Upvotes: 3