Recipe 20.12. Using Cooperative Super calls with Terser SyntaxCredit: Michele Simionato, Gonçalo Rodrigues ProblemYou 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. SolutionA 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 DiscussionHere 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_ _": 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 ! Methods that want to access the super-instance just need to use 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 AlsoLibrary Reference and Python in a Nutshell docs on module inspect and the super built-in. |