24

Is there a way that I can define __init__ so keywords defined in **kwargs are assigned to the class?

For example, if I were to initialize a ValidationRule class with ValidationRule(other='email'), the value for self.other should be added to the class without having to explicitly name every possible kwarg.

class ValidationRule:
    def __init__(self, **kwargs):
        # code to assign **kwargs to .self
Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
mpen
  • 272,448
  • 266
  • 850
  • 1,236

7 Answers7

36

I think somewhere on the stackoverflow I've seen such solution. Anyway it can look like:

class ValidationRule:
    __allowed = ("other", "same", "different")
    def __init__(self, **kwargs):
        for k, v in kwargs.iteritems():
            assert( k in self.__class__.__allowed )
            setattr(self, k, v)

This class will only accept arguments with a whitelisted attribute names listed in __allowed.

ony
  • 12,457
  • 1
  • 33
  • 41
  • ValidationRule is just my base class, and it doesn't have any of its own attributes, so there isn't anything they can accidentally overwrite, but otherwise, this would be a good safe guard ;) I'd probably use a blacklist instead of a whitelist in this case though, so as not to restrict them too much. – mpen Mar 29 '10 at 05:40
  • 1
    Python-3 renamed `dict.iteritems -> dict.items`, [more information](https://stackoverflow.com/questions/30418481/error-dict-object-has-no-attribute-iteritems) – Mr. Hobo Jan 05 '21 at 08:10
  • I don't know if this is a python3 issue, but sending an invalid `kwargs` raises `AssertionError` in python 3.8. I used `try...catch` to overcome this – Mr. Hobo Jan 05 '21 at 08:14
  • @Mr.Hobo, you should be able to see source of assertion, and most likely it will lead to line `assert( k in self.__class__.__allowed )` which is there to justify last sentence of the answer about validation and it is the sole role of it ;) I.e. it can be removed if you don't like that or replaced with different way of handling that. – ony Jan 11 '21 at 19:41
20

This may not be the cleanest way, but it works:

class ValidationRule: 
    def __init__(self, **kwargs): 
        self.__dict__.update(kwargs)

I think I prefer ony's solution because it restricts available properties to keep you out of trouble when your input comes from external sources.

Community
  • 1
  • 1
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • 1
    It won't work correctly if variable is a "magic" one. Consider [propeties](http://docs.python.org/2/library/functions.html#property) and [slotted objects](http://docs.python.org/2/reference/datamodel.html?highlight=__slots__#slots). That applies to both Python 2 and Python 3. – WGH Sep 07 '13 at 11:55
13

You could do something like this:

class ValidationRule:
   def __init__(self, **kwargs):
      for (k, v) in kwargs.items():
         setattr(self, k, v)
sth
  • 222,467
  • 53
  • 283
  • 367
  • 2
    If the kwargs list is long, you might want to use *iteritems()* instead of *items()*. For your purpose that should be fine though. – Georg Schölly Mar 29 '10 at 10:25
  • @GeorgSchölly What's the difference between `iteritems()` and `items()`? – Stevoisiak Feb 06 '18 at 19:17
  • @StevenVascellaro In Python 2 `iteritems()`, returns a generator while `items()` returns a list. In Python 3, `items()` returns an itemview and `iteritems()` was removed. I prefer `items()` because it's compatible with both. For very large dicts, I still use `iteritems()`. See also https://stackoverflow.com/questions/13998492/iteritems-in-python – Georg Schölly Feb 07 '18 at 08:03
4
class ValidationRule:
   def __init__(self, **kwargs):
      self.__dict__.update(kwargs)
newacct
  • 119,665
  • 29
  • 163
  • 224
3

You can set your kwargs arguments by updating __dict__ attribute of the instance.

class ValidationRule:
   def __init__(self, **kwargs):
       self.__dict__.update(kwargs)
Ruslan Spivak
  • 1,700
  • 1
  • 11
  • 5
3

This could be considered nicer than updating __dict__:

class C:
    def __init__(self, **kwargs):
        vars(self).update(kwargs)

>>> c = C(a='a', b='b')
>>> c.a   # returns 'a'
>>> c.b   # returns 'b'
Greg
  • 1,894
  • 18
  • 11
0

I found the above answers helpful and then refined:

class MyObj(object):
    def __init__(self, key1=1, key2=2, key3=3):
        for (k, v) in locals().iteritems():
            if k != 'self':
                setattr(self, k, v)

Test:

>>> myobj = MyObj(key1=0)
>>> print myobj.key1
0

And validation is also there:

>>> myobj = MyObj(key4=4)
TypeError: __init__() got an unexpected keyword argument 'key4'
user394430
  • 2,805
  • 2
  • 28
  • 27