147

So I was following Python's Super Considered Harmful, and went to test out his examples.

However, Example 1-3, which is supposed to show the correct way of calling super when handling __init__ methods that expect different arguments, flat-out doesn't work.

This is what I get:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

It seems that object itself violates one of the best practices mentioned in the document, which is that methods which use super must accept *args and **kwargs.

Now, obviously Mr. Knight expected his examples to work, so is this something that was changed in recent versions of Python? I checked 2.6 and 2.7, and it fails on both.

So what is the correct way to deal with this problem?

cha0site
  • 10,517
  • 3
  • 33
  • 51
  • 2
    My preferred way is: Flat and simple inheritance hierarchies. – millimoose Jan 23 '12 at 14:06
  • 28
    You should also read ["Python's super() considered super"](http://rhettinger.wordpress.com/2011/05/26/super-considered-super/) to get a balanced view :) – Björn Pollex Jan 23 '12 at 14:06
  • @BjörnPollex: Thanks! Your link provides an answer to the question: You can write a "root class" which inherits from `object`, and it makes sure to call `object`'s `__init__` correctly. – cha0site Jan 23 '12 at 14:15
  • 8
    Note that `__init__` on `object` silently ignores any parameters on Python 2.5. This changed in Python 2.6. – Wilfred Hughes Apr 30 '14 at 15:40
  • 1
    @Wilfred: Hey, Thanks for answering the actual question! Now I know why the essay is out of date! – cha0site May 04 '14 at 20:32

3 Answers3

150

Sometimes two classes may have some parameter names in common. In that case, you can't pop the key-value pairs off of **kwargs or remove them from *args. Instead, you can define a Base class which unlike object, absorbs/ignores arguments:

class Base(object):
    def __init__(self, *args, **kwargs): pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

yields

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

Note that for this to work, Base must be the penultimate class in the MRO.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 4
    Shouldn't `Base` call `super(Base, self).__init__()`? – cha0site Jan 23 '12 at 14:47
  • 5
    For this to work, `Base` must come at the end of the MRO (except for `object`). Calling `object.__init__` does nothing, so it is okay not to call `super(Base, self).__init__()`. In fact, I think it may be clearer not to include `super(Base, self).__init__()` to drive home the point that `Base` is the end of the line. – unutbu Jan 23 '12 at 14:59
38

If you're going to have a lot of inheritence (that's the case here) I suggest you to pass all parameters using **kwargs, and then pop them right after you use them (unless you need them in upper classes).

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

This is the simplest way to solve those kind of problems.

third = Third(first_arg=1, second_arg=2, third_arg=3)
juliomalegria
  • 24,229
  • 14
  • 73
  • 89
  • 2
    This is nice but then one loses the explicit nature of the arguments, such as their name and possible default values. Is there a way to keep this explicit part ? – Adrien Mau Oct 24 '22 at 09:23
12

As explained in Python's super() considered super, one way is to have class eat the arguments it requires, and pass the rest on. Thus, when the call-chain reaches object, all arguments have been eaten, and object.__init__ will be called without arguments (as it expects). So your code should look like this:

class A(object):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(object):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(*args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(*args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(*args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)
Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • 1
    So, what about `E(arg = 10)`? I don't like this method, I need to know the MRO in advance in order to use it. The other method where I write a "root class" that inherits from `object` seems much cleaner. – cha0site Jan 23 '12 at 14:20
  • This method is useful if the functions called do not share argument-names. I admit that I do not have much practical experience with `super`, so this approach might indeed be rather theoretical. – Björn Pollex Jan 23 '12 at 14:25