Instead of writing code like this 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:
There are two kinds of valid attribute names: data attributes and methods. The other kind of instance attribute reference is a method. A method is a function that “belongs to” an object. (In Python, the term method is not unique to class instances: other object types can have methods as well.
Attributes of a class can also be accessed using the following built-in methods and functions : getattr() – This function is used to access the attribute of object. hasattr() – This function is used to check if an attribute exist or not. setattr() – This function is used to set an attribute.
The __dict__ attribute will return a dictionary object of module attributes, functions and other definitions and their respective values. dir() is a built-in function that also returns the list of all attributes and functions in a module. Learn more about module attributes in Python Docs.
There are some things about the autoassign code that bug me (mostly stylistic, but one more 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 like a decorator. But autoassign(*argnames)
calls a function which returns a decorator. To achieve this magic, autoassign
needs to test the type of its first argument. If given a choice, I prefer functions not test the type of its arguments.
There seems to be a considerable amount of code devoted to setting up sieve
, lambdas within lambdas, ifilters, and lots 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 a simpler way. (See below).
for _ in itertools.starmap(assigned.setdefault, defaults): pass
. I don't think map
or starmap
was meant to call functions, whose only purpose is their side effects. It could have been written more clearly with the mundane:
for key,value in defaults.iteritems(): assigned.setdefault(key,value)
Here is an alternative simpler implementation which has the same functionality as autoassign (e.g. can do includes and excludes), and which addresses 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 is the unit test I used to check 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
is compatible with IPython code completion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With