Python: Iterating through constructor's arguments

I often find myself writing class constructors like this:

class foo:
    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

This can obviously become a pain if the number of arguments (and class attributes) gets high. I'm looking for the most pythonic way to loop through the constructor's arguments list and assign attributes accordingly. I'm working with Python 2.7, so ideally I'm looking for help with that version.

Answers


The most Pythonic way is what you've already written. If you are happy to require named arguments, you could do this:

class foo:
    def __init__(self, **kwargs):
        vars(self).update(kwargs)

Provided answers rely on *vargs and **kargs arguments, which might not be convenient at all if you want to restrict to a specific set of arguments with specific names: you'll have to do all the checking by hand.

Here's a decorator that stores the provided arguments of a method in its bound instance as attributes with their respective names.

import inspect
import functools

def store_args(method):
    """Stores provided method args as instance attributes."""
    argspec = inspect.getargspec(method)
    defaults = dict(zip( argspec.args[-len(argspec.defaults):], argspec.defaults ))
    arg_names = argspec.args[1:]
    @functools.wraps(method)
    def wrapper(*positional_args, **keyword_args):
        self = positional_args[0]
        # Get default arg values
        args = defaults.copy()
        # Add provided arg values
        list(map( args.update, ( zip(arg_names, positional_args[1:]), keyword_args.items() ) ))
        # Store values in instance as attributes
        self.__dict__.update(args)
        return method(*positional_args, **keyword_args)

    return wrapper

You can then use it like this:

class A:
    @store_args
    def __init__(self, a, b, c=3, d=4, e=5):
        pass

a = A(1,2)
print(a.a, a.b, a.c, a.d, a.e)

Result will be 1 2 3 4 5 on Python3.x or (1, 2, 3, 4, 5) on Python2.x


class foo:
    def __init__(self, **kwargs):
        for arg_name, arg_value in kwargs.items():
            setattr(self, arg_name, arg_value)

This requires arguments to be named:

obj = foo(arg1 = 1, arg2 = 2)

You can do that both for positional and for keyword arguments:

class Foo(object):
    def __init__(self, *args, **kwargs):
        for arg in args:
            print arg
        for kwarg in kwargs:
            print kwarg

* packs positional arguments into a tuple and ** keyword arguments into a dictionary:

foo = Foo(1, 2, 3, a=4, b=5, c=6) // args = (1, 2, 3), kwargs = {'a' : 4, ...}

How about this?

class foo:
    def __init__(self, arg1, arg2, arg3):
        for _prop in dir():
            setattr(self, _prop, locals()[_prop])

This uses the builtin python dir function to iterate over all local variables. It has a minor side effect of creating an extraneous self reference but you could filter that if you really wanted. Also if you were to declare any other locals before the dir() call, they would get added as constructed object's attributes as well.


As others have noted, you should probably stick to your original 'pythonic' method in most cases.

However, if you really want to go the whole nine yards, here's some code that neatly deals with args, keyword args if desired, and avoids boilerplate repetition:

def convert_all_args_to_attribs(self, class_locals):
    class_locals.pop('self')
    if 'kwargs' in class_locals:
        vars(self).update(class_locals.pop('kwargs'))
    vars(self).update(class_locals)

class FooCls:
    def __init__(self, foo, bar):
        convert_all_args_to_attribs(self, locals())

class FooClsWithKeywords:
    def __init__(self, foo, bar, **kwargs):
        convert_all_args_to_attribs(self, locals())

f1 = FooCls(1,2)
f2 = FooClsWithKeywords(3,4, cheese='stilton')


print vars(f1) #{'foo': 1, 'bar': 2}
print vars(f2) #{'cheese': 'stilton', 'foo': 3, 'bar': 4}

What about iterating over the explicit variable names?

I.e.

class foo:
    def __init__(self, arg1, arg2, arg3):
        for arg_name in 'arg1,arg2,arg3'.split(','):
            setattr(self, arg_name, locals()[arg_name])

f = foo(5,'six', 7)

Resulting with

print vars(f)

{'arg1': 5, 'arg2': 'six', 'arg3': 7}

My suggestion is similar to @peepsalot, except it's more explicit and doesn't use dir(), which according to the documentation

its detailed behavior may change across releases


the *args is a sequence so you can access the items using indexing:

def __init__(self, *args):
        if args:       
            self.arg1 = args[0]    
            self.arg2 = args[1]    
            self.arg3 = args[2]    
            ...  

or you can loop through all of them

for arg in args:
   #do assignments

Need Your Help

Api controller declaring more than one Get statement

c# asp.net-mvc-4 asp.net-web-api asp.net-routing

Using the new Api Controller in MVC4, and I've found a problem. If I have the following methods:

Data got committed in another/same session, cannot update row (Oracle SQL Developer)

oracle oracle-sqldeveloper

I occasionally get this error when i try to update a record through the grid.