Set class attributes from default arguments
I would like to automatically set the default argument as class attributes. For example, I have (with a lot of arguments, like a dozen or two):
class Foo:
def __init__(self,a=1,b=2,c=3,...):
self.a = a
self.b = b
self.c = c
...
And I want to automatically detect the attributes without rewriting self.x = x
all the time in the body __init__
.
I could use something like:
class Foo:
def __init__(self, **kwargs):
for attr, value in kwargs.items():
setattr(self,attr,value)
But now I cannot give them default values. I would like to get some function that gives me a dictionary of arguments with default values:
class Foo:
def __init__(self,a=1,b=2,c=3,...,**kwargs):
defargs = somefunction()
C defargs
will {'a':1,'b':2,'c':3,...}
(but does not contain kwargs).
The closest thing I managed to do:
class Foo:
def __init__(self,a=1,b=2,c=3,...,**kwargs):
defargs=locals()
defargs.pop('self')
defargs.pop('kwargs')
for attr, value in defargs.items():
setattr(self,attr,value)
But I don't know if this code contains any potentially dangerous behavior.
source to share
In Python 3.3+ this can be done easily using inspect.signature
:
import inspect
def auto_assign(func):
signature = inspect.signature(func)
def wrapper(*args, **kwargs):
instance = args[0]
bind = signature.bind(*args, **kwargs)
for param in signature.parameters.values():
if param.name != 'self':
if param.name in bind.arguments:
setattr(instance, param.name, bind.arguments[param.name])
if param.name not in bind.arguments and param.default is not param.empty:
setattr(instance, param.name, param.default)
return func(*args, **kwargs)
wrapper.__signature__ = signature # Restore the signature
return wrapper
class Foo:
@auto_assign
def __init__(self, foo, bar, a=1, b=2, c=3):
pass
f = Foo(10, 20)
g = Foo(100, 200, a=999)
print(f.__dict__)
print(g.__dict__)
print(inspect.getargspec(Foo.__init__))
Output:
{'bar': 20, 'b': 2, 'foo': 10, 'c': 3, 'a': 1}
{'bar': 200, 'b': 2, 'foo': 100, 'c': 3, 'a': 999}
ArgSpec(args=['self', 'foo', 'bar', 'a', 'b', 'c'], varargs=None, keywords=None, defaults=(1, 2, 3))
source to share
You can always set the actual class attributes (you are referencing the instance attributes) to keep the default values, and either get them explicitly:
class Foo:
# default attribute values
a = 1
...
def __init__(self, **kwargs):
setattr(self, 'a', kwargs.get('a', getattr(self, 'a')))
...
Or just leave them to be accessed normally ( not recommended for mutable attributes):
class Foo:
# default attribute values
a = 1
...
def __init__(self, **kwargs):
if 'a' in kwargs:
setattr(self, 'a', kwargs['a'])
...
Foo().a
Get you 1
and Foo(a=2).a
get you anyway 2
, and now you can easily refactor the loop on ('a', ...)
for the names of the corresponding attributes.
source to share