I often find myself writing class constructors like this:
class foo:
def __init__(self, arg1, arg2, arg3):
self.arg1 = arg1
self.arg2 = arg2
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
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
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)
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)
For python >= 3.7 take a look at the @dataclass class decorator.
Among other things, it handles your __init__
boilerplate coding problem.
From the doc:
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_on_hand: int = 0
Will automatically add an __init__
like that:
def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand