How can you set class attributes from variable arguments (kwargs) in python

Suppose I have a class with a constructor (or other function) that takes a variable number of arguments and then sets them as attributes of the class conditionally.

I could set them manually, but it seems that variable parameters are quite common in python, so there must be a common idiom for this. But I'm not sure how to do it dynamically.

I have an example using eval, but this is hardly safe. I want to know the right way to do this - maybe with a lambda?

class Foo: def setAllManually(self, a=None, b=None, c=None): if a!=None: self.a = a if b!=None: self.b = b if c!=None: self.c = c def setAllWithEval(self, **kwargs): for key in **kwargs: if kwargs[param] != None eval("self." + key + "=" + kwargs[param]) 
+79
python
Nov 18 '11 at 18:18
source share
10 answers

You can update the __dict__ attribute (which represents class 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 can filter keys in advance (use iteritems instead of items if you are still using Python 2.x).

+89
Nov 18 '11 at 18:39
source share

You can use the setattr() method:

 class Foo: def setAllWithKwArgs(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) 

There is a similar getattr() for getting attributes.

+125
Nov 18 '11 at 18:27
source share

Most of the answers here do not cover a good way to initialize all allowed attributes with just one default value. So, to add to the answers given by @fqxp and @mmj :

 class Myclass: def __init__(self, **kwargs): # all those keys will be initialized as class attributes allowed_keys = set(['attr1','attr2','attr3']) # initialize all allowed keys to false self.__dict__.update((key, False) for key in allowed_keys) # and update the given keys by their given values self.__dict__.update((key, value) for key, value in kwargs.items() if key in allowed_keys) 
+9
Nov 16 '16 at 12:12
source share

I offer a fqxp answer option that, in addition to the allowed attributes , allows you to set default values for the attributes:

 class Foo(): def __init__(self, **kwargs): # define default attributes default_attr = dict(a=0, b=None, c=True) # define (additional) allowed attributes with no default value more_allowed_attr = ['d','e','f'] allowed_attr = list(default_attr.keys()) + more_allowed_attr default_attr.update(kwargs) self.__dict__.update((k,v) for k,v in default_attr.items() if k in allowed_attr) 

This is Python 3.x code, for Python 2.x you need at least one adjustment, iteritems() instead of items() .

+5
Aug 14 '15 at 10:24
source share
 class SymbolDict(object): def __init__(self, **kwargs): for key in kwargs: setattr(self, key, kwargs[key]) x = SymbolDict(foo=1, bar='3') assert x.foo == 1 

I called the SymbolDict class because it is essentially a dictionary that works using characters instead of strings. In other words, instead of x['foo'] you execute x.foo , but the same thing happens under the covers.

+2
Nov 18 '11 at 18:36
source share

The following solutions vars(self).update(kwargs) or self.__dict__.update(**kwargs) are not reliable, as the user can enter any dictionary without error messages. If I need to check that the user inserts the following signature ('a1', 'a2', 'a3', 'a4', 'a5'), the solution does not work. In addition, the user should be able to use the object by passing "positional parameters" or "parameters of kay-value pairs".

Therefore, I propose the following solution using a metaclass.

 from inspect import Parameter, Signature class StructMeta(type): def __new__(cls, name, bases, dict): clsobj = super().__new__(cls, name, bases, dict) sig = cls.make_signature(clsobj._fields) setattr(clsobj, '__signature__', sig) return clsobj def make_signature(names): return Signature( Parameter(v, Parameter.POSITIONAL_OR_KEYWORD) for v in names ) class Structure(metaclass = StructMeta): _fields = [] def __init__(self, *args, **kwargs): bond = self.__signature__.bind(*args, **kwargs) for name, val in bond.arguments.items(): setattr(self, name, val) if __name__ == 'main': class A(Structure): _fields = ['a1', 'a2'] if __name__ == '__main__': a = A(a1 = 1, a2 = 2) print(vars(a)) a = A(**{a1: 1, a2: 2}) print(vars(a)) 
+2
Nov 26 '18 at 12:57
source share

this is the easiest with larsks

 class Foo: def setAllWithKwArgs(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) 

my example:

 class Foo: def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) door = Foo(size='180x70', color='red chestnut', material='oak') print(door.size) #180x70 
+1
Aug 26 '16 at 5:10
source share

They may be the best solution, but the following comes to my mind:

 class Test: def __init__(self, *args, **kwargs): self.args=dict(**kwargs) def getkwargs(self): print(self.args) t=Test(a=1, b=2, c="cats") t.getkwargs() python Test.py {'a': 1, 'c': 'cats', 'b': 2} 
0
Nov 18 '11 at 18:30
source share

Another option based on the excellent answers mmj and fqxp . What if we want

  1. Avoid hard coding the list of allowed attributes
  2. Direct and explicit set of default values ​​for each attribute in the constructor
  3. Limit kwargs to predefined attributes either
    • silently rejecting false arguments or, conversely,
    • raising a mistake.

By "directly" I mean to avoid the extraneous default_attributes dictionary.

 class Bar(object): def __init__(self, **kwargs): # Predefine attributes with default values self.a = 0 self.b = 0 self.A = True self.B = True # get a list of all predefined values directly from __dict__ allowed_keys = list(self.__dict__.keys()) # Update __dict__ but only for keys that have been predefined # (silently ignore others) self.__dict__.update((key, value) for key, value in kwargs.items() if key in allowed_keys) # To NOT silently ignore rejected keys rejected_keys = set(kwargs.keys()) - set(allowed_keys) if rejected_keys: raise ValueError("Invalid arguments in constructor:{}".format(rejected_keys)) 

Not a big breakthrough, but maybe someone will come in handy ...

EDIT: If our class uses @property decorators to encapsulate “protected” attributes using get and set methods, and if we want to be able to set these properties using our constructor, we might want to expand the allowed_keys list allowed_keys values ​​from dir(self) , the following :

 allowed_keys = [i for i in dir(self) if "__" not in i and any([j.endswith(i) for j in self.__dict__.keys()])] 

The code above excludes

  • any hidden variable from dir() (the exception is based on the presence of "__"), and
  • any method from dir() whose name is not found at the end of the attribute name (protected or otherwise) from __dict__.keys() , therefore, only methods decorated with @property are most likely saved.

This change is probably only valid for Python 3 and above.

0
Jan 08 '19 at 21:15
source share

I suspect that in most cases it would be better to use named args (for better code for self-documentation), so that it looks something like this:

 class Foo: def setAll(a=None, b=None, c=None): for key, value in (a, b, c): if (value != None): settattr(self, key, value) 
-one
Nov 18 '11 at 18:36
source share



All Articles