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: Automatic attribute assignment
Submitter: Arnaud Delobelle (other recipes)
Last Updated: 2008/03/11
Version no: 1.0
Category: OOP

 

5 stars 3 vote(s)


Description:

This recipe defines a decorator @autoassign that makes methods assign some or all of their arguments automatically to attributes of self.

Source: Text Source

from functools import wraps
from inspect import getargspec, isfunction
from itertools import izip, ifilter, starmap

def autoassign(*names, **kwargs):
    """
    autoassign(function) -> method
    autoassign(*argnames) -> decorator
    autoassign(exclude=argnames) -> decorator
    
    allow a method to assign (some of) its arguments as attributes of
    'self' automatically.  E.g.
    
    >>> class Foo(object):
    ...     @autoassign
    ...     def __init__(self, foo, bar): pass
    ... 
    >>> breakfast = Foo('spam', 'eggs')
    >>> breakfast.foo, breakfast.bar
    ('spam', 'eggs')
    
    To restrict autoassignment to 'bar' and 'baz', write:
    
        @autoassign('bar', 'baz')
        def method(self, foo, bar, baz): ...

    To prevent 'foo' and 'baz' from being autoassigned, use:

        @autoassign(exclude=('foo', 'baz'))
        def method(self, foo, bar, baz): ...
    """
    if kwargs:
        exclude, f = set(kwargs['exclude']), None
        sieve = lambda l:ifilter(lambda nv: nv[0] not in exclude, l)
    elif len(names) == 1 and isfunction(names[0]):
        f = names[0]
        sieve = lambda l:l
    else:
        names, f = set(names), None
        sieve = lambda l: ifilter(lambda nv: nv[0] in names, l)
    def decorator(f):
        fargnames, _, _, fdefaults = getargspec(f)
        # Remove self from fargnames and make sure fdefault is a tuple
        fargnames, fdefaults = fargnames[1:], fdefaults or ()
        defaults = list(sieve(izip(reversed(fargnames), reversed(fdefaults))))
        @wraps(f)
        def decorated(self, *args, **kwargs):
            assigned = dict(sieve(izip(fargnames, args)))
            assigned.update(sieve(kwargs.iteritems()))
            for _ in starmap(assigned.setdefault, defaults): pass
            self.__dict__.update(assigned)
            return f(self, *args, **kwargs)
        return decorated
    return f and decorator(f) or decorator

#---------- Examples of use ------------------

>>> class Test(object): 
...      @autoassign('foo', 'bar')
...      def __init__(self, foo, bar=3, baz=6):
...          "some clever stuff going on here"
...          print 'baz =', baz 
... 
>>> class Test2(object):
...     @autoassign
...     def __init__(self, foo, bar): pass
... 
>>> class Test3(object):
...     @autoassign(exclude=('foo', 'bar'))
...     def __init__(self, foo, bar, baz=5, **kwargs): pass
... 
>>> t = Test(1, 2, 5) 
baz = 5
>>> u = Test(foo=8)
baz = 6
>>> v = Test2(10, 11)
>>> w = Test3(100, 101, foobar=102)
>>> 
>>> print Test.__init__.__doc__
some clever stuff going on here
>>> 
>>> print t.foo
1
>>> print t.bar
2
>>> 
>>> print u.foo
8
>>> print u.bar
3
>>> 
>>> print v.foo, v.bar # 10 11
10 11
>>> print w.baz, w.foobar # 5 102
5 102
>>> for obj, attr in ('w', 'foo'), ('w', 'bar'), ('t', 'baz'):
...     try:
...         getattr(globals()[obj], attr)
...     except AttributeError:
...         print '%s.%s raises AttributeError' % (obj, attr)
... 
w.foo raises AttributeError
w.bar raises AttributeError
t.baz raises AttributeError
>>>

Discussion:

This recipe was first suggested in a thread on comp.lang.python:

http://groups.google.com/group/comp.lang.python/browse_thread/thread/32b421bbe6caaeed/7a1bd9ff3b5b3ca2

The original idea for an 'autoassign' decorator came form Diez B. Roggisch (see thread above), I modified it a bit and improved it after feedback from several people on c.l.p.



Add comment

Number of comments: 3

Excellent, Vladimir Pouzanov, 2008/03/11
Excellent snippet and very useful too!
Add comment

Excellent, Vladimir Pouzanov, 2008/03/11
Excellent snippet and very useful too!
Add comment

3 more alternate versions, Drew Perttula, 2008/03/11
Here's another one: http://bigasterisk.com/darcs/?r=waterworks;a=headblob;f=/waterworks/Tools.py

def initialize(ob, args):
    """In __init__, call initialize(self, locals()) to load all passed 
    arguments."""
and http://bigasterisk.com/post/makeattrs has another one:
def make_attributes_from_args(*argnames):
    """
    This function simulates the effect of running
      self.foo=foo
    for each of the given argument names ('foo' in the example just
    now). Now you can write:
        def __init__(self,foo,bar,baz):
            copy_to_attributes('foo','bar','baz')
            ...
    instead of:
        def __init__(self,foo,bar,baz):
            self.foo=foo
            self.bar=bar
            self.baz=baz
            ... 
    """
    ...code...


    """
    ----------------------------------------------------------------
    More discussion:
    
    I feel that the non-standard function (this one) is justified even
    though it is less clear than the comb code above. The comb code
    version is as offensive to me as this is:
      mylist.append('a')
      mylist.append('b')
      mylist.append('c')
      
    That code, while clear, punishes both the reader and programmer.
    I hope that none of you would ever write the above, and that you'd
    use something like "mylist.extend(['a','b','c'])" instead. My
    make_attributes_from_args function is to be used in an analagous
    manner. (Yes, certain situations will warrant code that is
    repetitive in some way or another. But I'm talking about
    self.foo=foo stuff, not those situations.)

    Another way to look at the attribute-assignment problem: There's
    only one 'program step' going on in the block-- some locals are
    getting copied to instance variables. One thing deserves one line.

    The disadvantage of the function is that it makes ordinary code
    harder to read for programmers who haven't read these
    instructions.  My only weapon against that problem is the choice
    of the function's name.  I deliberately chose 'make' and 'args'
    instead of 'set' and 'locals' because I want the reader to
    immediately think of the new attributes that are (probably) being
    created out of the arguments to __init__. The fact that this
    function will operate on any locals and set existing attributes to
    new values is incidental.

    I would be interested to hear better names for the function.  If I
    get a better one, I will convert all my code immediately.

    For another look at the same problem, see:
      http://twistedmatrix.com/users/acapnotic/keeparg.py.html
    """

Add comment



Highest rated recipes:

1. A simple XML-RPC server

2. Web service accessible ...

3. Wrapping template engine ...

4. Assignment in expression

5. SOLVING THE METACLASS ...

6. Povray for python

7. Calling Windows API ...

8. Generic filter logic ...

9. Function Decorators by ...

10. MS SQL Server log monitor




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