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)
Asked By: HexxNine

||

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.

Answered By: Jonathan Mayer

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.

Answered By: Jonathan Mayer

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
Answered By: chepner
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.