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

240voto

fqxp Points 1170

Vous pouvez mettre à jour le __dict__ (qui représente les attributs de l'instance sous la forme d'un dictionnaire) avec les arguments du mot-clé :

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

alors vous pouvez le faire :

>>> bar = Bar(a=1, b=2)
>>> bar.a
1

et avec quelque chose comme :

allowed_keys = {'a', 'b', 'c'}
self.__dict__.update((k, v) for k, v in kwargs.items() if k in allowed_keys)

vous pouvez filtrer les clés au préalable (utiliser iteritems au lieu de items si vous utilisez encore Python 2.x).

186voto

larsks Points 23184

Vous pouvez utiliser le setattr() méthode :

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

Il existe un système analogue getattr() pour récupérer les attributs.

20voto

La plupart des réponses ici ne couvrent pas une bonne façon d'initialiser tous les attributs autorisés à une seule valeur par défaut. Ainsi, pour compléter les réponses données par @fqxp y @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)

16voto

mmj Points 634

Je propose une variante de Réponse de fqxp qui, en plus de attributs autorisés permet de définir valeurs par défaut pour les attributs :

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)

Il s'agit de code Python 3.x, pour Python 2.x vous avez besoin d'au moins un ajustement, iteritems() à la place de items() .

SUIVI TRÈS TARDIF

J'ai récemment réécrit le code ci-dessus sous la forme d'un fichier décorateur de classe afin de réduire au minimum le codage des attributs. D'une certaine manière, il ressemble à certaines caractéristiques de la les @dataclass décorateur et c'est ce que vous pourriez vouloir utiliser à la place.

# class decorator definition
def classattributes(default_attr,more_allowed_attr):
    def class_decorator(cls):
        def new_init(self,*args,**kwargs):
            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)
        cls.__init__ = new_init
        return cls
    return class_decorator

# usage:
# 1st arg is a dict of attributes with default values
# 2nd arg is a list of additional allowed attributes which may be instantiated or not
@classattributes( dict(a=0, b=None, c=True) , ['d','e','f'] )
class Foo():
    pass # add here class body except __init__

@classattributes( dict(g=0, h=None, j=True) , ['k','m','n'] )
class Bar():
    pass # add here class body except __init__

obj1 = Foo(d=999,c=False)
obj2 = Bar(h=-999,k="Hello")

obj1.__dict__ # {'a': 0, 'b': None, 'c': False, 'd': 999}
obj2.__dict__ # {'g': 0, 'h': -999, 'j': True, 'k': 'Hello'}

11voto

billjoie Points 73

Une autre variante basée sur les excellentes réponses de mmj y fqxp . Et si nous voulions

  1. Éviter de coder en dur une liste d'attributs autorisés
  2. Directement et définir explicitement des valeurs par défaut pour chaque attribut dans le constructeur
  3. Restreindre les kwargs aux attributs prédéfinis soit par
    • rejeter silencieusement les arguments non valables ou , alternativement,
    • ce qui entraîne une erreur.

Par "directement", j'entends le fait d'éviter une default_attributes dictionnaire.

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

Il ne s'agit pas d'une avancée majeure, mais elle peut être utile à quelqu'un...

EDITAR: Si notre classe utilise @property pour encapsuler les attributs "protégés" avec des getters et des setters, et si nous voulons être en mesure de définir ces propriétés avec notre constructeur, nous pourrions vouloir étendre le décorateur allowed_keys avec des valeurs de dir(self) comme suit :

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

Le code ci-dessus exclut

  • toute variable cachée de dir() (exclusion basée sur la présence de "__"), et
  • n'importe quelle méthode de dir() dont le nom ne se trouve pas à la fin d'un nom d'attribut (protégé ou non) de __dict__.keys() ce qui permet de ne conserver que les méthodes décorées de @propriétés.

Cette modification n'est probablement valable que pour Python 3 et plus.

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