Recipe20.11.Allowing Chaining of Mutating List Methods


Recipe 20.11. Allowing Chaining of Mutating List Methods

Credit: Stephan Diehl, Alex Martelli

Problem

The 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.

Solution

A 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

Discussion

Mutator 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 Also

Library Reference and Python in a Nutshell docs on built-in type list and special methods _ _new_ _ and _ _getattr_ _.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net