ASPN ActiveState Programmer Network  
ActiveState, a division of Sophos
/ Home / Perl / PHP / Python / Tcl / XSLT /
/ Safari / My ASPN /
Cookbooks | Documentation | Mailing Lists | Modules | News Feeds | Products | User Groups
Submit Recipe
My Recipes

All Recipes
All Cookbooks


View by Category

Title: Changing return value for mutating list methods (meta programming)
Submitter: Stephan Diehl (other recipes)
Last Updated: 2003/09/13
Version no: 1.2
Category: OOP

 

5 stars 2 vote(s)


Description:

Mutating list methods such as 'append' or 'extend' return None instead of the (mutated) list itself. Sometimes, this is not the desired behaviour.
The Discussion on comp.lang.python resulted in the following solution.
The shown code is my own, while two other solutions are presented in the discussion.

Source: Text Source

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)

Discussion:

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:

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.


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)

def wrapReturning(func):
    def returningWrapper(*args, **kwds):
        func(*args, **kwds)
        return args[0]
    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,
mutator))
                break

        # delegate the rest to built-in 'type'
        return type.__new__(mcl, className, classBases, classDict)

class Returner: __metaclass__ = metaReturner


# example usage

class returnerlist(Returner, list):
    __mutators__ = 'sort reverse append extend insert'.split()

print returnerlist('hello').extend('ciao').sort().reverse()


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.



Add comment

Number of comments: 3

Typo, andy mckay, 2003/09/12
There is a NameError on meth, I believe line 13 should read:

classdict[method] = wrapedmeth(classname,method)

Add comment

Corrected, Stephan Diehl, 2003/09/13
Yes, looks like a typo (I'm sure, I did test the code before posting :-) It's corrected now.
Add comment

another shortcut (Runsun Pan), Runsun Pan, 2004/11/15

Returning a modified list can be achieved with fewer code using 
the "x or y" logic: aList.method(arg) or aList >>> L = range(5) >>> L [0, 1, 2, 3, 4] >>> L.insert(3,10) >>> L [0, 1, 2, 10, 3, 4] >>> L.append(-10) >>> L [0, 1, 2, 10, 3, 4, -10] >>> L.extend([3,4,5]) >>> L [0, 1, 2, 10, 3, 4, -10, 3, 4, 5] >>> L.sort() >>> L [-10, 0, 1, 2, 3, 3, 4, 4, 5, 10] The above procedures can be achieved in one line of code without
involving any additional function: >>> m = range(5) >>> (((m.insert(3,10) or m).append(-10) or m).extend([3,4,5]) or m).sort() or m [-10, 0, 1, 2, 3, 3, 4, 4, 5, 10] Certainly, this is a lot more difficult to read. However, in
many cases, especially you perform only one or two operations
(but not a lot series like the example above): m.sort() or m (m.sort() or m).append(5) or m this approach looks more handy.

Add comment



Highest rated recipes:

1. A simple XML-RPC server

2. Web service accessible ...

3. Treat the Win32 Registry ...

4. Watching a directory ...

5. Union Find data structure

6. Function Decorators by ...

7. MS SQL Server log monitor

8. Table objects with ...

9. wx twisted support using ...

10. More accurate sum




Privacy Policy | Email Opt-out | Feedback | Syndication
© 2006 ActiveState Software Inc. All rights reserved.