Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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:

  1. Are there drawbacks or pitfalls associated with this shortcut?
  2. Is there a better way to achieve similar convenience?
like image 464
FMc Avatar asked Sep 06 '10 16:09

FMc


People also ask

What are the two types of attributes a Python object may have?

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.

How do you use attributes in Python?

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.

What are __ attributes in Python?

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.


1 Answers

There are some things about the autoassign code that bug me (mostly stylistic, but one more serious problem):

  1. 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' 
  2. 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.

  3. 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).

  4. 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.

like image 93
unutbu Avatar answered Sep 23 '22 08:09

unutbu