Recipe 6.20. Using Cooperative Supercalls Concisely and Safely
Credit: Paul McNett, Alex Martelli
Problem
You appreciate the
cooperative style of multiple-inheritance coding supported by the
super built-in, but you wish you could use that
style in a more terse and concise way.
Solution
A good solution is a
mixin classa class you can multiply inherit
from, that uses introspection to allow more terse
coding:
import inspectAny class cls that inherits from class
class SuperMixin(object):
def super(cls, *args, **kwargs):
frame = inspect.currentframe(1)
self = frame.f_locals['self']
methodName = frame.f_code.co_name
method = getattr(super(cls, self), methodName, None)
if inspect.ismethod(method):
return method(*args, **kwargs)
super = classmethod(super)
SuperMixin acquires a magic method named
super: calling cls.super(args)
from within a method named somename of class
cls is a concise way to call super(cls,
self).somename(args). Moreover, the call is safe even if no
class that follows cls in Method Resolution Order
(MRO) defines any method named somename.
Discussion
Here is a usage example:
if _ _name_ _ == '_ _main_ _':Python has been offering
class TestBase(list, SuperMixin):
# note: no myMethod defined here
pass
class MyTest1(TestBase):
def myMethod(self):
print "in MyTest1"
MyTest1.super( )
class MyTest2(TestBase):
def myMethod(self):
print "in MyTest2"
MyTest2.super( )
class MyTest(MyTest1, MyTest2):
def myMethod(self):
print "in MyTest"
MyTest.super( )
MyTest( ).myMethod( )
# emits:
# in MyTest
# in MyTest1
# in MyTest2
"new-style" classes for years, as a
preferable alternative to the classic classes that you get by
default. Classic classes exist only for backwards-compatibility with
old versions of Python and are not recommended for new code. Among
the advantages of new-style classes is the ease of calling superclass
implementations of a method in a
"cooperative" way that fully
supports multiple inheritance, thanks to the super
built-in.Suppose you have a method in a new-style class
cls, which needs to perform a task and then
delegate the rest of the work to the superclass implementation of the
same method. The code idiom is:
def somename(self, *args):This idiom suffers from two minor issues: it's
...some preliminary task...
return super(cls, self).somename(*args)
slightly verbose, and it also depends on a superclass offering a
method somename. If you want to make
cls less coupled to other classes, and therefore
more robust, by removing the dependency, the code gets even more
verbose:
def somename(self, *args):The mixin class SuperMixin shown
...some preliminary task...
try:
super_method = super(cls, self).somename
except AttributeError:
return None
else:
return super_method(*args)
in this recipe removes both issues. Just ensure
cls inherits, directly or indirectly, from
SuperMixin (alongside any other base classes you
desire), and then you can code, concisely and
robustly:
def somename(self, *args):The classmethod SuperMixin.super
...some preliminary task...
return cls.super(*args)
relies on simple introspection to get the self
object and the name of the method, then internally uses built-ins
super and getattr to get the
superclass method, and safely call it only if it exists. The
introspection is performed through the handy
inspect module of the standard Python library,
making the whole task even simpler.
See Also
Library Reference and Python in a
Nutshell docs on super, the new object
model and MRO, the built-in getattr, and standard
library module inspect; Recipe 20.12 for another recipe taking a
very different approach to simplify the use of built-in
super.