Mutating list methods such as 'append' or 'extend' return None instead of the (mutated) list itself. Sometimes, this is not the desired behaviour. The <a href="http://groups.google.de/groups?dq=&hl=de&lr=&ie=UTF-8&oe=UTF-8&threadm=I706b.17723%24hE5.626547%40news1.tin.it&prev=/groups%3Fdq%3D%26num%3D25%26hl%3Dde%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26group%3Dcomp.lang.python%26start%3D75"> Discussion</a> on comp.lang.python resulted in the following solution. The shown code is my own, while two other solutions are presented in the discussion.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
def wrapedmeth(classname,meth): def _meth(self,*argl,**argd): getattr(super(globals()[classname],self),meth)(*argl,**argd) return self return _meth class ReturnMeta(type): def __new__(cls,classname,bases,classdict): wrap = classdict.get('return_self_super_methods') if wrap is not None: for method in wrap: classdict[method] = wrapedmeth(classname,method) return super(ReturnMeta,cls).__new__(cls,classname,bases,classdict) class mylist(list): __metaclass__ = ReturnMeta return_self_super_methods = ['append','extend','insert','remove','reverse','sort'] if __name__ == '__main__': print 'l = [1,2]' print 'mylist: print l.append(3)' l = mylist([1,2]) print l.append(3) print 'list: print l.append(3)' l = [1,2] print l.append(3)
To have a reference to the (mutated) list returned is usefull, if one wants to chain commands such as mylistinstance.append(7).sort(). GvR (and others) took the decision not to allow this because it could be confusing programmers who thought that such methods would return a new object. This leads to problems where the sideeffects are desired. Expressions inside lambda functions and list comprehension comes to mind.
During the news list dicussion, two alternative implementations were presented. The first one is from Jacek Generowicz and doesn't involve meta classes: <pre> class returner:
def __init__(self, object): self.object = object def __getattr__(self, name): def proxy(*args, **kwds): getattr(self.object, name)(*args, **kwds) return self.object return proxy
lst = [1,2,3] print lst.append(4) # Here you get None print returner(lst).append(5) # Here you get the modified list. </pre>
The second one is from Alex Martelli and is (as far as I can see) virtually the same than my own solution (Alex was answering Jacek and didn't know the original solution)
<pre> def wrapReturning(func): def returningWrapper(args, *kwds): func(args, *kwds) return args return returningWrapper
class metaReturner(type): ''' simplified metaReturner: deal with single inheritance only '''
def __new__(mcl, className, classBases, classDict): # get the "real" base class, then wrap its mutators for base in classBases: if not isinstance(base, metaReturner): for mutator in classDict['__mutators__']: classDict[mutator] = wrapReturning(getattr(base,
# delegate the rest to built-in 'type' return type.__new__(mcl, className, classBases, classDict)
class Returner: __metaclass__ = metaReturner
class returnerlist(Returner, list): __mutators__ = 'sort reverse append extend insert'.split()
print returnerlist('hello').extend('ciao').sort().reverse() </pre>
I think that the metaclass approach is more flexible than the 'traditional' one, but requires more classmagic and is probably not as understandable.
Both metaclass solutions have the problem, that these methods can not be overwritten. For example, if I would define my own 'append' method in 'mylist' (and had forgotten what the metaclass is doing), it would be silently ignored and this could lead to a very frustating programming experience.