181 votes

Comment définir les attributs d'une classe à partir d'arguments variables (kwargs) en python ?

Supposons que j'aie une classe avec un constructeur (ou une autre fonction) qui prend un nombre variable d'arguments et les définit comme attributs de la classe de manière conditionnelle.

Je pourrais les définir manuellement, mais il semble que les paramètres variables soient suffisamment courants en Python pour qu'il y ait un idiome commun pour faire cela. Mais je ne suis pas sûr de savoir comment le faire dynamiquement.

J'ai un exemple utilisant eval, mais c'est loin d'être sûr. Je voudrais savoir quelle est la bonne façon de faire -- peut-être avec 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])

4voto

AntoJs Points 5214

Les solutions suivantes vars(self).update(kwargs) o self.__dict__.update(**kwargs) ne sont pas robustes, car l'utilisateur peut entrer n'importe quel dictionnaire sans message d'erreur. Si je dois vérifier que l'utilisateur insère la signature suivante ('a1', 'a2', 'a3', 'a4', 'a5'), la solution ne fonctionne pas. De plus, l'utilisateur devrait pouvoir utiliser l'objet en passant les "paramètres positionnels" ou les "paramètres de paires de valeurs".

Je propose donc la solution suivante en utilisant une métaclasse.

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))

3voto

Oleg_Kornilov Points 11

Celui-ci est le plus facile via larsks

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

mon exemple :

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

3voto

Nasseh Khodaie Points 21

Voici ce que je fais habituellement :

class Foo():
    def __init__(self, **kwrgs):
        allowed_args = ('a', 'b', 'c')
        default_values = {'a':None, 'b':None} 
        self.__dict__.update(default_values)
        if set(kwrgs.keys()).issubset(allowed_args):
            self.__dict__.update(kwrgs)
        else:
            unallowed_args = set(kwrgs.keys()).difference(allowed_args)
            raise Exception (f'The following unsupported argument(s) is passed to Foo:\n{unallowed_args}')

Dans la plupart des cas, je trouve que ce code court suffit.

Note

L'utilisation de setattr avec une boucle for peut avoir un impact négatif sur les performances de votre code, en particulier si vous créez beaucoup de cette classe. Dans mon test, en remplaçant l'élément __update__ avec setattr(self, key, value) dans une boucle for pour la classe Foo ci-dessus, a rendu l'instanciation de la classe 1,4 fois plus longue. Ce phénomène est d'autant plus important que le nombre d'arguments à définir est élevé. Les boucles for sont lentes en Python, ce n'est donc pas une surprise.

2voto

wberry Points 6068
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

J'ai appelé la classe SymbolDict car il s'agit essentiellement d'un dictionnaire qui utilise des symboles au lieu de chaînes de caractères. En d'autres termes, vous faites x.foo au lieu de x['foo'] mais sous les couvertures, c'est la même chose qui se passe.

2voto

bn0 Points 41

Les deux setattr() y __dict__.update() méthodes de contournement de la propriété @setter fonctions. La seule façon que j'ai trouvée pour que cela fonctionne est d'utiliser exec() .

exec est considéré comme un risque pour la sécurité, mais nous ne l'utilisons pas avec n'importe quelle entrée utilisateur, donc je ne vois pas pourquoi ce serait le cas. Si vous n'êtes pas d'accord, j'aimerais vraiment savoir pourquoi, alors laissez un commentaire. Je ne veux pas être le défenseur ou l'auteur d'un code non sécurisé.

Mon exemple est en grande partie emprunté aux réponses précédentes pour des raisons d'exhaustivité, mais la principale différence est la ligne exec(f"self.{key} = '{value}'")

class Foo:
    def __init__(self, **kwargs):
        # Predefine attributes with default values
        self.a = 0
        self.b = 0
        self.name = " "

        # get a list of all predefined attributes
        allowed_keys = [attr for attr in dir(self) if not attr.startswith("_")]
        for key, value in kwargs.items():
            # Update properties, but only for keys that have been predefined 
            # (silently ignore others)
            if key in allowed_keys:
                exec(f"self.{key} = '{value}'")

    @property
    def name(self):
        return f"{self.firstname} {self.lastname}"

    @name.setter
    def name(self, name):
        self.firstname, self.lastname = name.split(" ", 2)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X