This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Incorrect behaviour of super() in a metaclass-created subclass
Type: behavior Stage:
Components: Interpreter Core Versions: Python 3.3, Python 3.4, Python 3.5
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: jdetrey, r.david.murray, ronaldoussoren
Priority: normal Keywords:

Created on 2014-02-10 12:13 by jdetrey, last changed 2022-04-11 14:57 by admin.

Files
File name Uploaded Description Edit
bug.py jdetrey, 2014-02-10 12:13 Test case
Messages (2)
msg210829 - (view) Author: Jérémie Detrey (jdetrey) * Date: 2014-02-10 12:13
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.
msg210837 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-02-10 14:22
I don't think you can successfully use the no-argument version of super in this context.  The no-argument form depends on compile-time magic, and you are mucking about with what object is what during the compilation phase.

But I'm no expert on how super works.
History
Date User Action Args
2022-04-11 14:57:58adminsetgithub: 64780
2014-02-25 20:19:20ronaldoussorensetnosy: + ronaldoussoren
2014-02-10 14:22:41r.david.murraysetnosy: + r.david.murray
messages: + msg210837
2014-02-10 13:52:37jdetreysetversions: + Python 3.3
2014-02-10 12:13:22jdetreycreate