Dear all,
I've been noticing a strange (and probably incorrect) behaviour of the super() function when using a metaclass in order to automatically replace a class declaration by a subclass of itself.
In the attached test case, the function `wrap' is used to create such a subclass: given the original class `cls' in input, it declares and returns the subclass `Wrapper(cls)' whose constructor calls the original one from `cls':
>>> def wrap(cls):
... print("In wrap() for class %s" % cls.__name__)
... name = cls.__name__
... class Wrapper(cls):
... def __init__(self, *args, **kwargs):
... print("In Wrapper(%s).__init__()" % name)
... super().__init__(*args, **kwargs)
... return Wrapper
...
When `wrap' is used as a decorator (as for class `B' in the test case), the correct behaviour is observed: the identifier `B' now represents the `Wrapper(B)' subclass, the MRO is as expected (`Wrapper', then the original class `B', then `object'), and instantiating a `B' object correctly calls the constructors in the right order:
>>> @wrap
... class B:
... def __init__(self):
... print("In B.__init__()")
... super().__init__()
...
In wrap() for class B
>>> B.mro()
[<class '__main__.wrap.<locals>.Wrapper'>, <class '__main__.B'>, <class 'object'>]
>>> B()
In Wrapper(B).__init__()
In B.__init__()
<__main__.wrap.<locals>.Wrapper object at 0x7fa8c6adb410>
Now, let us automatically call the `wrap' function from a metaclass' `__new__' method upon declaration of the class (such as class `A' in the test case):
>>> class Metaclass(type):
... def __new__(meta, name, bases, namespace):
... print("In Metaclass.__new__() for class %s" % name)
... cls = super().__new__(meta, name, bases, namespace)
... return cls if cls.__name__ == 'Wrapper' else wrap(cls)
...
>>> class A(metaclass = Metaclass):
... def __init__(self):
... print("In A.__init__()")
... super().__init__()
...
In Metaclass.__new__() for class A
In wrap() for class A
In Metaclass.__new__() for class Wrapper
The MRO still looks correct:
>>> A.mro()
[<class '__main__.wrap.<locals>.Wrapper'>, <class '__main__.A'>, <class 'object'>]
However, instantiating the class `A' creates an infinite recursion in the original constructor `A.__init__', just as if the `super()' call had somehow gotten confused between the original class declared as `A' and its subclass, which is now referred to by the identifier `A':
>>> A()
In Wrapper(A).__init__()
In A.__init__()
In A.__init__()
In A.__init__()
In A.__init__()
[...]
Maybe I'm doing something wrong somewhere, but it seems to me that the behaviour should at least be the same for classes `A' and `B'.
Kind regards,
Jérémie.
|