Is there a better way to update an object's __dict__ with kwargs?
Question:
This is the current approach I am taking to update an objects dict from the keyword args passed into it and it works just fine. Is this an acceptable way? Is there a better way?
class MyClass():
def __init__(self, **kwargs):
default_settings = {
'style': None, 'mods': None, 'filt': True,
'gsize': 600, 'scale': False
}
default_settings.update(kwargs)
self.__dict__.update(default_settings)
# ...
if __name__ == '__main__':
test = MyClass(style='some_style', filt=False, scale=True)
Answers:
Suggestions:
You lose the ability to check for typos in the keys of your constructor. At a minimum, you probably want to throw an exception if a key in kwargs isn’t also a key in default_settings. Maybe this:
bad_keys = [k for k in kwargs.keys() if k not in default_settings]
if bad_keys:
raise TypeError(
"Invalid arguments for MyClass.__init__: %r" % bad_keys)
You might want default_settings to be a static member of MyClass, though then you’d have to modify your code to:
self.__dict__.update(default_settings)
self.__dict__.update(kwargs)
… instead of modifying the local default_settings instance.
Now that I’ve been around the block a few more times, I’ve learned that a better answer might be to just use the attrs
package and allow the decorator to create a constructor for you.
For example:
from attrs import define, field
from typing import Optional
@define
class MyClass:
style: Optional[str] = field(default=None)
mods: Optional[int] = field(default=None)
filt: bool = field(default=True)
gsize: int = field(default=600)
scale: bool = field(default=False)
Done! The attrs class will create a constructor for you that will accept both args and kwargs style initialization.
Don’t use kwargs
just to reduce the amount of typing. If you have specific parameters you expect, list them.
class MyClass:
def __init__(self, *, style=None, mods=None, filt=True, gsize=600, scale=False):
self.style = style
self.mods = mods
self.filt = filt
self.gsize = gsize
self.scale = scale
if __name__ == '__main__':
test = MyClass(style='some_style', filt=False, scale=True)
Similar to https://stackoverflow.com/a/75644958/1126841, you can use the dataclasses
module in the standard library to reduce the amount of boilerplate involved here.
from dataclasses import dataclass
from typing import Optional
@dataclass
class MyClass:
style: Optional[str] = None
mods: Optional[str] = None
filt: bool = True
gsize: int = 600
scale: bool = False
This is the current approach I am taking to update an objects dict from the keyword args passed into it and it works just fine. Is this an acceptable way? Is there a better way?
class MyClass():
def __init__(self, **kwargs):
default_settings = {
'style': None, 'mods': None, 'filt': True,
'gsize': 600, 'scale': False
}
default_settings.update(kwargs)
self.__dict__.update(default_settings)
# ...
if __name__ == '__main__':
test = MyClass(style='some_style', filt=False, scale=True)
Suggestions:
You lose the ability to check for typos in the keys of your constructor. At a minimum, you probably want to throw an exception if a key in kwargs isn’t also a key in default_settings. Maybe this:
bad_keys = [k for k in kwargs.keys() if k not in default_settings]
if bad_keys:
raise TypeError(
"Invalid arguments for MyClass.__init__: %r" % bad_keys)
You might want default_settings to be a static member of MyClass, though then you’d have to modify your code to:
self.__dict__.update(default_settings)
self.__dict__.update(kwargs)
… instead of modifying the local default_settings instance.
Now that I’ve been around the block a few more times, I’ve learned that a better answer might be to just use the attrs
package and allow the decorator to create a constructor for you.
For example:
from attrs import define, field
from typing import Optional
@define
class MyClass:
style: Optional[str] = field(default=None)
mods: Optional[int] = field(default=None)
filt: bool = field(default=True)
gsize: int = field(default=600)
scale: bool = field(default=False)
Done! The attrs class will create a constructor for you that will accept both args and kwargs style initialization.
Don’t use kwargs
just to reduce the amount of typing. If you have specific parameters you expect, list them.
class MyClass:
def __init__(self, *, style=None, mods=None, filt=True, gsize=600, scale=False):
self.style = style
self.mods = mods
self.filt = filt
self.gsize = gsize
self.scale = scale
if __name__ == '__main__':
test = MyClass(style='some_style', filt=False, scale=True)
Similar to https://stackoverflow.com/a/75644958/1126841, you can use the dataclasses
module in the standard library to reduce the amount of boilerplate involved here.
from dataclasses import dataclass
from typing import Optional
@dataclass
class MyClass:
style: Optional[str] = None
mods: Optional[str] = None
filt: bool = True
gsize: int = 600
scale: bool = False