How can you set class attributes from variable arguments (kwargs) in python
You could update the __dict__
attribute (which represents the instance attributes in the form of a dictionary) with the keyword arguments:
class Bar(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
then you can:
>>> bar = Bar(a=1, b=2)
>>> bar.a
1
and with something like:
allowed_keys = {'a', 'b', 'c'}
self.__dict__.update((k, v) for k, v in kwargs.items() if k in allowed_keys)
you could filter the keys beforehand (use iteritems
instead of items
if you’re still using Python 2.x).
Set class attributes with keyword arguments
The type
function can take arguments that allow it to create a new class dynamically:
B = type('B', (), d)
b = B()
print(b.x, b.y)
Output:
1 2
The first argument is the name of the generated class. The second is a tuple
containing its base classes. In particular, the following two snippets are (roughly) equivalent:
class D(A, B, C):
pass
and:
D = type('D', (A, B, C), {})
The last argument is a dict
mapping names to attributes (both methods and values).
How do I use **kwargs in Python 3 class __init__ function?
General kwargs ideas
When you load variables with self.var = value
, it adds it to an internal dictionary that can be accessed with self.__dict__
.
class Foo1:
def __init__(self, **kwargs):
self.a = kwargs['a']
self.b = kwargs['b']
foo1 = Foo1(a=1, b=2)
print(foo1.a) # 1
print(foo1.b) # 2
print(foo1.__dict__) # {'a': 1, 'b': 2}
If you want to allow for arbitrary arguments, you can leverage the fact that kwargs
is also a dictionary and use the update()
function.
class Foo2:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
foo2 = Foo2(some_random_variable=1, whatever_the_user_supplies=2)
print(foo2.some_random_variable) # 1
print(foo2.whatever_the_user_supplies) # 2
print(foo2.__dict__) # {'some_random_variable': 1, 'whatever_the_user_supplies': 2}
This will prevent you from getting an error when you try to store a value that isn't there
class Foo3:
def __init__(self, **kwargs):
self.a = kwargs['a']
self.b = kwargs['b']
foo3 = Foo3(a=1) # KeyError: 'b'
If you wanted to ensure that variables a
or b
were set in the class regardless of what the user supplied, you could create class attributes or use kwargs.get()
class Foo4:
def __init__(self, **kwargs):
self.a = kwargs.get('a', None)
self.b = kwargs.get('b', None)
foo4 = Foo4(a=1)
print(foo4.a) # 1
print(foo4.b) # None
print(foo4.__dict__) # {'a': 1, 'b': None}
However, with this method, the variables belong to the class rather than the instance. This is why you see foo5.b
return a string, but it's not in foo5.__dict__
.
class Foo5:
a = 'Initial Value for A'
b = 'Initial Value for B'
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
foo5 = Foo5(a=1)
print(foo5.a) # 1
print(foo5.b) # Initial Value for B
print(foo5.__dict__) # {'a': 1}
If you are giving the users the freedom to specify any kwargs
they want, you can iterate through the __dict__
in a function.
class Foo6:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def do_something(self):
for k, v in self.__dict__.items():
print(f"{k} -> {v}")
foo6 = Foo6(some_random_variable=1, whatever_the_user_supplies=2)
foo6.do_something()
# some_random_variable -> 1
# whatever_the_user_supplies -> 2
However, depending on whatever else you have going on in your class, you might end up with a lot more instance attributes than the user supplied. Therefore, it might be good to have the user supply a dictionary as an argument.
class Foo7:
def __init__(self, user_vars):
self.user_vars = user_vars
def do_something(self):
for k, v in self.user_vars.items():
print(f"{k} -> {v}")
foo7 = Foo7({'some_random_variable': 1, 'whatever_the_user_supplies': 2})
foo7.do_something()
# some_random_variable -> 1
# whatever_the_user_supplies -> 2
Addressing your code
With your updated code, I would suggest using the self.__dict__.update(kwargs)
method. Then you can either raise an error when you don't encounter variable you're relying on (option1
method) or you can have a default value for the variable incase it's not defined (option2
method)
class MathematicalModel:
def __init__(self, var1, var2, var3, **kwargs):
self.var1 = var1
self.var2 = var2
self.var3 = var3
self.__dict__.update(kwargs) # Store all the extra variables
class MathematicalModelExtended(MathematicalModel):
def __init__(self, var1, var2, var3, **kwargs):
super().__init__(var1, var2, var3, **kwargs)
def option1(self):
# Trap error if you need var4 to be specified
if 'var4' not in self.__dict__:
raise ValueError("Please provide value for var4")
x = (self.var1 + self.var2 + self.var3) / self.var4
return x
def option2(self):
# Use .get() to provide a default value when the user does not provide it.
_var4 = self.__dict__.get('var4', 1)
x = (self.var1 + self.var2 + self.var3) / self.var4
return x
a = MathematicalModel(1, 2, 3)
b = MathematicalModelExtended(1, 2, 3, var4=4, var5=5, var6=6)
print(b.option1()) # 1.5
print(b.option2()) # 1.5
Granted, if MathematicalModel
will never use anything other than var1
, var2
, and var3
, there's no point in passing the kwargs
.
class MathematicalModel:
def __init__(self, var1, var2, var3, **kwargs):
self.var1 = var1
self.var2 = var2
self.var3 = var3
class MathematicalModelExtended(MathematicalModel):
def __init__(self, var1, var2, var3, **kwargs):
super().__init__(var1, var2, var3)
self.__dict__.update(kwargs)
def option1(self):
# Trap error if you need var4 to be specified
if 'var4' not in self.__dict__:
raise ValueError("Please provide value for var4")
x = (self.var1 + self.var2 + self.var3) / self.var4
return x
def option2(self):
# Use .get() to provide a default value when the user does not provide it.
_var4 = self.__dict__.get('var4', 1)
x = (self.var1 + self.var2 + self.var3) / self.var4
return x
a = MathematicalModel(1, 2, 3)
b = MathematicalModelExtended(1, 2, 3, var4=4, var5=5, var6=6)
print(b.option1()) # 1.5
print(b.option2()) # 1.5
Trying to use **kwargs to define attributes in a class
kwargs
is just a mapping; it doesn't magically create local variables for your function. You need to index the python dictionary with the desired key.
def __init__(self, **kwargs):
self.health = kwargs['health']
self.dodge = kwargs['dodge']
self.damage = kwargs['damage']
self.critAdd = kwargs['critAdd']
A dataclass simplifies this:
from dataclasses import dataclass
@dataclass
class Character:
health: int
dodge: int
damage: int
critAdd: float
This generates your original __init__
automatically.
If you need to do additional work in __init__
after adding the dataclass decorator you can define __post_init__
which a dataclass will call after __init__
.
How to properly set Python class attributes from init arguments
This is exactly what the dataclasses
module is for. You use it like this:
from dataclasses import dataclass
@dataclass
class Foo:
attr1: str
attr2: int
attr3: bool
# ...etc...
That gives you a class with an implicit __init__
method that takes
care of all the parameter setting for you. You also get things like a
useful __repr__
and __str__
predefined:
>>> myvar = Foo(attr1="somevalue", attr2=42, attr3=False)
>>> myvar
Foo(attr1='somevalue', attr2=42, attr3=False)
>>> print(myvar)
Foo(attr1='somevalue', attr2=42, attr3=False)
Python 2.7: Is it good practice to set all args/kwargs attribute with setattr?
Prior discussion: Python decorator to automatically define __init__ variables, Python: Is it a good idea to dynamically create variables?
Pros:
- Reduces code duplication
Cons:
- Less readable
- Makes it harder to locate member references (thus may also upset static analyzers like
pylint
) - The amount of boilerplate code needed to handle all scenarios (e.g. a positional argument passed as a keyword one; check that all required arguments are present) makes code reduction nonexistent and duplicates stock subroutine invocation code
- Requiring a lot of arguments in a constructor that are to be simply set as members is an anti-pattern in and of itself
Class __init__ with mix of fixed and variable length **kwargs arguments
If you pass **kwargs to the constructor it will work, you need to unpack the dict elements into the functions. See also pass **kwargs argument to another function with **kwargs
Proper way to use **kwargs in Python
You can pass a default value to get()
for keys that are not in the dictionary:
self.val2 = kwargs.get('val2',"default value")
However, if you plan on using a particular argument with a particular default value, why not use named arguments in the first place?
def __init__(self, val2="default value", **kwargs):
Related Topics
Split a Python List into Other "Sublists" I.E Smaller Lists
Unsupported Operand Type(S) for +: 'Int' and 'Str'
How to Convert a Time.Struct_Time Object into a Datetime Object
How to Clone a Django Model Instance Object and Save It to the Database
How to Stop Flask Application Without Using Ctrl-C
How to Do/Workaround a Conditional Join in Python Pandas
Beginner Python: Reading and Writing to the Same File
Python's Insert Returning None
Usage of Sys.Stdout.Flush() Method
Pandas/Python: Set Value of One Column Based on Value in Another Column
How to Clamp an Integer to Some Range
Python: How to Make the Ansi Escape Codes to Work Also in Windows
How to Set Up a Virtual Environment for Python in Visual Studio Code