[New-bugs-announce] [issue20581] Incorrect behaviour of super() in a metaclass-created subclass

Jérémie Detrey report at bugs.python.org
Mon Feb 10 13:13:22 CET 2014

New submission from Jérémie Detrey:

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,

components: Interpreter Core
files: bug.py
messages: 210829
nosy: jdetrey
priority: normal
severity: normal
status: open
title: Incorrect behaviour of super() in a metaclass-created subclass
type: behavior
versions: Python 3.4, Python 3.5
Added file: http://bugs.python.org/file34020/bug.py

Python tracker <report at bugs.python.org>

More information about the New-bugs-announce mailing list