1

In a method in a class, I need to call super() of the class where the method is defined, not the class of the object. I specifically care about Python 2.x, but 3.x solutions are welcome.

I want to have generic boilerplate that can be used throughout a project, where one class inherits from another, to an unknown depth. To avoid recursion errors, I need to call the parent class of the current class where the code is defined, not the parent of the self object. There are a number of places in each module where the current class name is referenced, as many methods are extended, and each extended method may call the parent class method.

Here's demo that fails:

#!/usr/bin/env python

from inspect import stack

class Foo(object):
    def __init__(self):
        print "Foo.__init__()"
        print "file: %s" % __file__
        print "class: %s" % self.__class__.__name__
        print "stack: %s" % stack()[0][3]
        print

class Bar(Foo):
    def __init__(self):
        print "Bar.__init__()"
        super(type(self), self).__init__()

class Baz(Bar):
    pass


foo = Foo()
bar = Bar()
baz = Baz()

Here is the result:

Foo.__init__()
file: ./class.py
class: Foo
stack: __init__

Bar.__init__()
Foo.__init__()
file: ./class.py
class: Bar
stack: __init__

Bar.__init__()
Bar.__init__()

# snip several hundred lines

Bar.__init__()
Bar.__init__()
Traceback (most recent call last):
  File "./class.py", line 24, in <module>
    baz = Baz()
  File "./class.py", line 16, in __init__
    super(type(self), self).__init__()

# snip several hundred lines

  File "./class.py", line 16, in __init__
    super(type(self), self).__init__()
  File "./class.py", line 16, in __init__
    super(type(self), self).__init__()
RuntimeError: maximum recursion depth exceeded while calling a Python object
Quantum Mechanic
  • 625
  • 1
  • 6
  • 20
  • ' to an unknown depth' sounds like a bad object design. –  Apr 28 '16 at 15:06
  • @DisplayName: "to an unknown depth" means the code grows over time. In my case, the code is a test suite, with some tests inheriting from existing tests, and overriding or extending existing methods. Further, each test is expected to have a half dozen predefined methods, that are called in a specific way from the test framework. – Quantum Mechanic May 01 '16 at 12:12
  • ...basically, 2.x has no pronoun for __this_class__. – Quantum Mechanic May 01 '16 at 12:20

1 Answers1

1

As you already found out using type(self) leads to recursions because Baz.__init__ is directed to Bar.__init__ but in there you want to call super(Baz, self).__init__ which is again Bar.__init__ so you end up with an infinite recursion.

Generally it's convention that you only call the parent without knowing on which class-instance you've called it because otherwise you'll need to really know and fix your MRO. You can always find out which child-class called the method with self.__class__ or self.__class__.__name__

Resolving it is quite simple: Replace your super-calls with:

super(Bar, self).__init__()

in python3 to avoid these (mostly unnecessary) hardcoding of classes in super it's also possible to use:

super().__init__()

That will always call the method on the next parent class that implements it. There are really seldom cases where the parent class of your current class is not the next parent class resolved by the super call.

The output then becomes (missing __file__):

Foo.__init__()
class: Foo
stack: __init__

Bar.__init__()
Foo.__init__()
class: Bar
stack: __init__

Bar.__init__()
Foo.__init__()
class: Baz
stack: __init__
MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • This seems relevant, http://stackoverflow.com/questions/5033903/python-super-method-and-calling-alternatives. – Bi Rico Apr 29 '16 at 22:19
  • @BiRico Since I'm using `super` consistently and this is linear inheritance it doesn't matter much. But definitly worth reading. I enjoyed it and now I'm not trusting `super` all that much anymore. Weird stuff ... and it probably won't ever be changed because of backwards-compatibility ... :-( Thank you for the link! – MSeifert Apr 29 '16 at 22:30
  • What I implied, but failed to spell out, is that I don't want to sprinkle the name of the current class throughout the code (in 2.x). So while `super(Bar, self).__init__()` works, it doesn't go far enough. I don't want to repeat the class name, I want a pronoun for it. But thanks for a very clear explanation. – Quantum Mechanic May 01 '16 at 12:07
  • @QuantumMechanic You stated you would also be interested in python 3.x solutions and that would be to use `super()` - without arguments. That would automatically just look for the "next" parent in the [MRO](https://docs.python.org/2/glossary.html). – MSeifert May 01 '16 at 16:00