Udun's Labs

Python's Super

First thing that comes to mind : why do I need to name the superclass explicitly, instead of passing in self.__class__ (or better still, type(self)) ? Because:

class Base(object):
    def method(self):
        print 'original'

class Derived(Base):
    def method(self):
        print 'derived'
        super(type(self), self).method()

class Subclass(Derived):
    def method(self):
        print 'subclass of derived'
        super(Subclass, self).method()

leads to:

>>> Subclass().method()
subclass of derived
derived
derived
derived
<...>
  File "<stdin>", line 4, in method
  File "<stdin>", line 4, in method
  File "<stdin>", line 4, in method
RuntimeError: maximum recursion depth exceeded while calling a Python object

And this pretty much explains the second question I had. In Python’s Super is nifty, but you can’t use it (Previously: Python’s Super Considered Harmful) we read:

People omit calls to super(...).__init__ if the only superclass is ‘object’, as, after all, object.__init__ doesn’t do anything! However, this is very incorrect. Doing so will cause other classes' __init__ methods to not be called.

But it does not say why (btw not all of the examples compile - see this link also for a nice hack when you need to have arguments passed in the init method, and can’t pop them one by one).

The why is multiple inheritance. Calling super(...).__init__ in a class whose only superclass is ‘object’ is useless. But when we have a MI hierarchy, and our “penultimate” base class (that is the one that inherits directly from object) is used as a superclass in an unrelated MI class the call super(...).__init__ will not delegate to object init but in the next class in the MRO. Example:

class Service(object):
    def __init__(self, host, binary, topic, manager, report_interval=None,
             periodic_interval=None, *args, **kwargs):
        print 'Initializing Service'
        super(Service, self).__init__(*args, **kwargs)

class Color(object):
    def __init__(self, color='red', **kwargs):
        print 'Initializing Color'
        self.color = color
        super(Color, self).__init__(**kwargs)

class ColoredService(Service, Color):
    def __init__(self, *args, **kwds):
        print 'Initializing Colored Service'
        super(ColoredService, self).__init__(*args, **kwds)

c = ColoredService('host', 'bin', 'top', 'mgr', 'ivl', color='blue')

gives:

Initializing Colored Service
Initializing Service
Initializing Color

Why ? The call super(Service, self).__init__(*args, **kwargs) calls the next method in self’s MRO - self being an instance of ColoredService. So the MRO is derived from the instance passed into super’s second argument (you may also pass a type, haven’t gone into this) while the starting point in the MRO is derived from super’s first argument.

super(startFromThisType, getTheMROFromThisInstance)

And that explains why one must always call super in a method meant to be overridden in a MI setting but when we need also the super class implementation.

Btw for Java people - there is no automatic constructor call in Python. You must call super.__init__ manually and that’s where this post comes from.

References:

You might want to visit my sponsors:

Comments