What is the best way to do automatic attribute assignment in Python, and is this a good idea?

Instead of writing such code every time I define a class:

class Foo(object): def __init__(self, a, b, c, d, e, f, g): self.a = a self.b = b self.c = c self.d = d self.e = e self.f = f self.g = g 

I could use this recipe for automatic attribute assignment .

 class Foo(object): @autoassign def __init__(self, a, b, c, d, e, f, g): pass 

Two questions :

  • Are there any flaws or errors associated with this shortcut?
  • Is there a better way to achieve similar convenience?
+23
python decorator attributes
06 Sep '10 at 16:13
source share
8 answers

There are some things related to the auto-association code that torment me (mostly stylistic, but another serious problem):

  • autoassign does not assign an args attribute:

     class Foo(object): @autoassign def __init__(self,a,b,c=False,*args): pass a=Foo('IBM','/tmp',True, 100, 101) print(a.args) # AttributeError: 'Foo' object has no attribute 'args' 
  • autoassign acts as a decorator. But autoassign(*argnames) calls a function that returns a decorator. To achieve this magic, autoassign needs to check the type of the first argument. If given a choice, I prefer the function not to test the type of its arguments.

  • It seems that there is a significant amount of code designed to configure sieve , lambda inside lambda, ifilters and a lot of conditions.

     if kwargs: exclude, f = set(kwargs['exclude']), None sieve = lambda l:itertools.ifilter(lambda nv: nv[0] not in exclude, l) elif len(names) == 1 and inspect.isfunction(names[0]): f = names[0] sieve = lambda l:l else: names, f = set(names), None sieve = lambda l: itertools.ifilter(lambda nv: nv[0] in names, l) 

    I think there might be an easier way. (See below).

  • for _ in itertools.starmap(assigned.setdefault, defaults): pass . I don’t think map or starmap intended to call functions whose sole purpose is their side effects. It could have been written more clearly with the things of the world:

     for key,value in defaults.iteritems(): assigned.setdefault(key,value) 

Here is an alternative simpler implementation that has the same functionality as the autoassign function (for example, can enable and disable), and which is addressed to the above points:

 import inspect import functools def autoargs(*include, **kwargs): def _autoargs(func): attrs, varargs, varkw, defaults = inspect.getargspec(func) def sieve(attr): if kwargs and attr in kwargs['exclude']: return False if not include or attr in include: return True else: return False @functools.wraps(func) def wrapper(self, *args, **kwargs): # handle default values if defaults: for attr, val in zip(reversed(attrs), reversed(defaults)): if sieve(attr): setattr(self, attr, val) # handle positional arguments positional_attrs = attrs[1:] for attr, val in zip(positional_attrs, args): if sieve(attr): setattr(self, attr, val) # handle varargs if varargs: remaining_args = args[len(positional_attrs):] if sieve(varargs): setattr(self, varargs, remaining_args) # handle varkw if kwargs: for attr, val in kwargs.items(): if sieve(attr): setattr(self, attr, val) return func(self, *args, **kwargs) return wrapper return _autoargs 

And here I used the unit test to test its behavior:

 import sys import unittest import utils_method as um class Test(unittest.TestCase): def test_autoargs(self): class A(object): @um.autoargs() def __init__(self,foo,path,debug=False): pass a=A('rhubarb','pie',debug=True) self.assertTrue(a.foo=='rhubarb') self.assertTrue(a.path=='pie') self.assertTrue(a.debug==True) class B(object): @um.autoargs() def __init__(self,foo,path,debug=False,*args): pass a=B('rhubarb','pie',True, 100, 101) self.assertTrue(a.foo=='rhubarb') self.assertTrue(a.path=='pie') self.assertTrue(a.debug==True) self.assertTrue(a.args==(100,101)) class C(object): @um.autoargs() def __init__(self,foo,path,debug=False,*args,**kw): pass a=C('rhubarb','pie',True, 100, 101,verbose=True) self.assertTrue(a.foo=='rhubarb') self.assertTrue(a.path=='pie') self.assertTrue(a.debug==True) self.assertTrue(a.verbose==True) self.assertTrue(a.args==(100,101)) def test_autoargs_names(self): class C(object): @um.autoargs('bar','baz','verbose') def __init__(self,foo,bar,baz,verbose=False): pass a=C('rhubarb','pie',1) self.assertTrue(a.bar=='pie') self.assertTrue(a.baz==1) self.assertTrue(a.verbose==False) self.assertRaises(AttributeError,getattr,a,'foo') def test_autoargs_exclude(self): class C(object): @um.autoargs(exclude=('bar','baz','verbose')) def __init__(self,foo,bar,baz,verbose=False): pass a=C('rhubarb','pie',1) self.assertTrue(a.foo=='rhubarb') self.assertRaises(AttributeError,getattr,a,'bar') def test_defaults_none(self): class A(object): @um.autoargs() def __init__(self,foo,path,debug): pass a=A('rhubarb','pie',debug=True) self.assertTrue(a.foo=='rhubarb') self.assertTrue(a.path=='pie') self.assertTrue(a.debug==True) if __name__ == '__main__': unittest.main(argv = sys.argv + ['--verbose']) 

PS. Using autoassign or autoargs compatible with IPython code completion.

+22
Sep 06 '10 at 16:48
source share

Is there a better way to achieve similar convenience?

I don't know if this will be better, but you can do it:

 class Foo(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) >>> foo = Foo(a = 1, b = 'bar', c = [1, 2]) >>> foo.a 1 >>> foo.b 'bar' >>> foo.c [1, 2] >>> 

Courtesy of Peter Norvig Python: non-answer questions .

+7
Sep 06 '10 at 16:30
source share

One drawback: many IDEs parse __init__.py to discover the attributes of an object. If you want automatic code completion in your IDE to be more functional, you might be better off writing this in an old fashioned way.

+6
06 Sep '10 at
source share

If you have many variables, you can pass one single configuration file or object.

+2
Sep 06 '10 at 16:20
source share

As above, although not the same ... the following is very short, dealing with args and kwargs :

 def autoassign(lcls): for key in lcls.keys(): if key!="self": lcls["self"].__dict__[key]=lcls[key] 

Use this:

 class Test(object): def __init__(self, a, b, *args, **kwargs): autoassign(locals()) 
+2
Apr 28 '15 at 16:50
source share

This is a simple implementation of judy2k :

 from inspect import signature def auto_args(f): sig = signature(f) # Get a signature object for the target: def replacement(self, *args, **kwargs): # Parse the provided arguments using the target signature: bound_args = sig.bind(self, *args, **kwargs) # Save away the arguments on `self`: for k, v in bound_args.arguments.items(): if k != 'self': setattr(self, k, v) # Call the actual constructor for anything else: f(self, *args, **kwargs) return replacement class MyClass: @auto_args def __init__(self, a, b, c=None): pass m = MyClass('A', 'B', 'C') print(m.__dict__) # {'a': 'A', 'b': 'B', 'c': 'C'} 
+1
Apr 25 '17 at 19:57
source share

In this package you can find

  • @autoargs inspired by answer-3653049
  • @autoprops to convert fields generated by @autoargs to @property for use with a validation library such as enforce or pycontracts .

Please note that this is confirmed for python 3.5+

+1
Aug 2 '17 at 14:03
source share
 class MyClass(object): def __init__(self, **kwargs): for key, value in kwargs.iteritems(): setattr(self, key, value) 

You simply cannot use * args, but you can store it in some list of instances (for example, self.args, you don’t know)

0
Sep 06 '10 at 19:23
source share