python-2.x

correct way to use super (argument passing)


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?


Solution

  • 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.