Recipe 20.11. Allowing Chaining of Mutating List MethodsCredit: Stephan Diehl, Alex Martelli ProblemThe methods of the list type that mutate a list object in placemethods such as append and sortreturn None. To call a series of such methods, you therefore need to use a series of statements. You would like those methods to return self to enable you to chain a series of calls within a single expression. SolutionA custom metaclass can offer an elegant approach to this task: def makeChainable(func): ''' wrapp a method returning None into one returning self ''' def chainableWrapper(self, *args, **kwds): func(self, *args, **kwds) return self # 2.4 only: chainableWrapper._ _name_ _ = func._ _name_ _ return chainableWrapper class MetaChainable(type): def _ _new_ _(mcl, cName, cBases, cDict): # get the "real" base class, then wrap its mutators into the cDict for base in cBases: if not isinstance(base, MetaChainable): for mutator in cDict['_ _mutators_ _']: if mutator not in cDict: cDict[mutator] = makeChainable(getattr(base, mutator)) break # delegate the rest to built-in 'type' return super(MetaChainable, mcl)._ _new_ _(mcl, cName, cBases, cDict) class Chainable: _ _metaclass_ _ = MetaChainable if _ _name_ _ == '_ _main_ _': # example usage class chainablelist(Chainable, list): _ _mutators_ _ = 'sort reverse append extend insert'.split( ) print ''.join(chainablelist('hello').extend('ciao').sort( ).reverse( )) # emits: oolliheca DiscussionMutator methods of mutable objects such as lists and dictionaries work in place, mutating the object they're called on, and return None. One reason for this behavior is to avoid confusing programmers who might otherwise think such methods build and return new objects. Returning None also prevents you from chaining a sequence of mutator calls, which some Python gurus consider bad style because it can lead to very dense code that may be hard to read. Some programmers, however, occasionally prefer the chained-calls, dense-code style. This style is particularly useful in such contexts as lambda forms and list comprehensions. In these contexts, the ability to perform actions within an expression, rather than in statements, can be crucial. This recipe shows one way you can tweak mutators' return values to allow chaining. Using a custom metaclass means the runtime overhead of introspection is paid only rarely, at class-creation time, rather than repeatedly. If runtime overhead is not a problem for your application, it may be simpler for you to use a delegating wrapper idiom that was posted to comp.lang.python by Jacek Generowicz: class chainable(object): def _ _init_ _(self, obj): self.obj = obj def _ _iter_ _(self): return iter(self.obj) def _ _getattr_ _(self, name): def proxy(*args, **kwds): result = getattr(self.obj, name)(*args, **kwds) if result is None: return self else: return result # 2.4 only: proxy._ _name_ _ = name return proxy The use of this wrapper is quite similar to that of classes obtained by the custom metaclass presented in this recipe's Solutionfor example: print ''.join(chainable(list('hello')).extend('ciao').sort( ).reverse( )) # emits: oolliheca See AlsoLibrary Reference and Python in a Nutshell docs on built-in type list and special methods _ _new_ _ and _ _getattr_ _. |