Recipe 20.12. Using Cooperative Super calls with Terser Syntax
Credit: Michele Simionato, Gonçalo
Rodrigues
Problem
You like 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 direct way.
Solution
A custom metaclass lets us selectively wrap the methods exposed by a
class. Specifically, if the second argument of a method is named
super, then that argument gets bound to the
appropriate instance of the built-in super:
import inspect
def second_arg(func):
args = inspect.getargspec(func)[0]
try: return args[1]
except IndexError: return None
def super_wrapper(cls, func):
def wrapper(self, *args, **kw):
return func(self, super(cls, self), *args, **kw)
# 2.4 only: wrapper._ _name_ _ = func._ _name_ _
return wrapper
class MetaCooperative(type):
def _ _init_ _(cls, name, bases, dic):
super(MetaCooperative, cls)._ _init_ _(cls, name, bases, dic)
for attr_name, func in dic.iteritems( ):
if inspect.isfunction(func) and second_arg(func) == "super":
setattr(cls, attr_name, super_wrapper(cls, func))
class Cooperative:
_ _metaclass_ _ = MetaCooperative
Discussion
Here is a usage example of the custom metaclass presented in this
recipe's Solution, in a typical toy case of
"diamond-shaped" inheritance:
if _ _name_ _ == "_ _main_ _":Methods that want to access the super-instance just need to use
class B(Cooperative):
def say(self):
print "B",
class C(B):
def say(self, super):
super.say( )
print "C",
class D(B):
def say(self, super):
super.say( )
print "D",
class CD(C, D):
def say(self, super):
super.say( )
print '!'
CD( ).say( )
# emits: B D C !
super as the name of their second argument; the
metaclass then arranges to wrap those methods so that the
super-instance gets synthesized and passed in as the second argument,
as needed.In other words, when a class cls, whose
metaclass is MetaCooperative, has methods whose
second argument is named super, then, in those
methods, any call of the form super.something(*args,
**kw) is a shortcut for super(cls,
self).something(*args, **kw). This
approach avoids the need to pass the class object as an argument to
the built-in super.Class cls may also perfectly well have
other methods that do not follow this convention, and in those
methods, it may use the built-in super in the
usual way: all it takes for any method to be
"normal" is to
not use super as the name of
its second argument, surely not a major restriction. This recipe
offers nicer syntax sugar for the common case of cooperative
supercalls, where the first argument to super is
the current classnothing more.
See Also
Library Reference and Python in a
Nutshell docs on module inspect and
the super built-in.