|
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)
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
>>> 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
>>> print w.baz, w.foobar
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
|
|
|