Définir les attributs du dictionnaire en python


102

Est-il possible de créer un objet à partir d'un dictionnaire en python de telle sorte que chaque clé soit un attribut de cet objet?

Quelque chose comme ça:

 d = { 'name': 'Oscar', 'lastName': 'Reyes', 'age':32 }

 e = Employee(d) 
 print e.name # Oscar 
 print e.age + 10 # 42 

Je pense que ce serait à peu près l'inverse de cette question: dictionnaire Python à partir des champs d'un objet

Réponses:


172

Bien sûr, quelque chose comme ça:

class Employee(object):
    def __init__(self, initial_data):
        for key in initial_data:
            setattr(self, key, initial_data[key])

Mettre à jour

Comme le suggère Brent Nash, vous pouvez rendre cela plus flexible en autorisant également les arguments de mots-clés:

class Employee(object):
    def __init__(self, *initial_data, **kwargs):
        for dictionary in initial_data:
            for key in dictionary:
                setattr(self, key, dictionary[key])
        for key in kwargs:
            setattr(self, key, kwargs[key])

Ensuite, vous pouvez l'appeler comme ceci:

e = Employee({"name": "abc", "age": 32})

ou comme ça:

e = Employee(name="abc", age=32)

ou même comme ça:

employee_template = {"role": "minion"}
e = Employee(employee_template, name="abc", age=32)

4
Si vous transmettez les données initiales, def __init__(self,**initial_data)vous obtenez l'avantage supplémentaire d'avoir une méthode init qui peut également faire des arguments de mots clés (par exemple "e = Employee (name = 'Oscar')" ou simplement prendre un dictionnaire (par exemple "e = Employee ( ** dict) ").
Brent écrit le code

2
Offrir à la fois les API Employee(some_dict)et les Employee(**some_dict)API est incohérent. Le meilleur des deux doit être fourni.
Mike Graham

4
Si vous définissez par défaut de votre arg au ()lieu de None, vous pouvez le faire comme si: def __init__(self, iterable=(), **kwargs): self.__dict__.update(iterable, **kwargs).
Matt Anderson

4
Je sais que c'est une vieille question, mais je veux juste ajouter que cela peut être fait en deux lignes avec la compréhension de la liste, par exemple:[[setattr(self,key,d[key]) for key in d] for d in some_dict]
TZ

2
J'aimerais savoir pourquoi cela n'est pas intégré à python d'une manière simple. J'utilise ceci pour gérer les lancements de l'API python GeoIP2 AddressNotFoundError(pour renvoyer des données corrigées dans ce cas, plutôt que de faire exploser) - j'ai trouvé fou que quelque chose de si facile en PHP ( (object) ['x' => 'y']) nécessite tellement de cruft en Python
Someguy123

46

Définir des attributs de cette manière n'est certainement pas la meilleure façon de résoudre un problème. Soit:

  1. Vous savez ce que tous les champs devraient être à l'avance. Dans ce cas, vous pouvez définir tous les attributs explicitement. Cela ressemblerait à

    class Employee(object):
        def __init__(self, name, last_name, age):
            self.name = name
            self.last_name = last_name
            self.age = age
    
    d = {'name': 'Oscar', 'last_name': 'Reyes', 'age':32 }
    e = Employee(**d) 
    
    print e.name # Oscar 
    print e.age + 10 # 42 

    ou

  2. Vous ne savez pas ce que tous les champs devraient être à l'avance. Dans ce cas, vous devez stocker les données sous forme de dict au lieu de polluer un espace de noms d'objets. Les attributs sont pour l'accès statique. Ce cas ressemblerait à

    class Employee(object):
        def __init__(self, data):
            self.data = data
    
    d = {'name': 'Oscar', 'last_name': 'Reyes', 'age':32 }
    e = Employee(d) 
    
    print e.data['name'] # Oscar 
    print e.data['age'] + 10 # 42 

Une autre solution qui est fondamentalement équivalente au cas 1 consiste à utiliser un collections.namedtuple. Voir la réponse de van pour savoir comment mettre en œuvre cela.


Et si le scénario se situait quelque part entre vos deux extrêmes? C'est précisément le cas d'utilisation pour cela, et actuellement AFAICT il n'y a aucun moyen de le faire de manière sèche et pythonique.
DylanYoung

15

Vous pouvez accéder aux attributs d'un objet avec __dict__et appeler la méthode de mise à jour dessus:

>>> class Employee(object):
...     def __init__(self, _dict):
...         self.__dict__.update(_dict)
... 


>>> dict = { 'name': 'Oscar', 'lastName': 'Reyes', 'age':32 }

>>> e = Employee(dict)

>>> e.name
'Oscar'

>>> e.age
32

3
__dict__est un artefact d'implémentation et ne doit pas être utilisé. De plus, cela ignore l'existence de descripteurs sur la classe.
Ignacio Vazquez-Abrams

1
@Ignacio que voulez-vous dire par "artefact d'implémentation"? Qu'est-ce que nous ne devrions pas en être conscient? Ou qu'il ne soit pas présent sur différentes plateformes? (par exemple. Python sous Windows vs Python sous Linux) Quelle serait une réponse acceptable?
OscarRyz

12
__dict__est une partie documentée du langage, pas un artefact d'implémentation.
Dave Kirby

3
L'utilisation setattrest préférable à l'accès __dict__direct. Vous devez garder à l'esprit beaucoup de choses qui pourraient conduire à __dict__ne pas être là ou à ne pas faire ce que vous voulez quand vous l'utilisez __dict__, mais cela setattrest pratiquement identique à ce que vous faites réellement foo.bar = baz.
Mike Graham

1
@DaveKirby: Il semble que l'utilisation générale de __dict__soit déconseillée: docs.python.org/tutorial/classes.html#id2
equaeghe

11

Pourquoi ne pas simplement utiliser les noms d'attributs comme clés d'un dictionnaire?

class StructMyDict(dict):

     def __getattr__(self, name):
         try:
             return self[name]
         except KeyError as e:
             raise AttributeError(e)

     def __setattr__(self, name, value):
         self[name] = value

Vous pouvez initialiser avec des arguments nommés, une liste de tuples, ou un dictionnaire, ou des attributions d'attributs individuels, par exemple:

nautical = StructMyDict(left = "Port", right = "Starboard") # named args

nautical2 = StructMyDict({"left":"Port","right":"Starboard"}) # dictionary

nautical3 = StructMyDict([("left","Port"),("right","Starboard")]) # tuples list

nautical4 = StructMyDict()  # fields TBD
nautical4.left = "Port"
nautical4.right = "Starboard"

for x in [nautical, nautical2, nautical3, nautical4]:
    print "%s <--> %s" % (x.left,x.right)

Sinon, au lieu de déclencher l'erreur d'attribut, vous pouvez renvoyer None pour les valeurs inconnues. (Une astuce utilisée dans la classe de stockage web2py)


7

Je pense que l'utilisation de la réponse settattrest la voie à suivre si vous avez vraiment besoin de soutien dict.

Mais si Employeeobject est simplement une structure à laquelle vous pouvez accéder avec dot syntax ( .name) au lieu de dict syntax ( ['name']), vous pouvez utiliser namedtuple comme ceci:

from collections import namedtuple

Employee = namedtuple('Employee', 'name age')
e = Employee('noname01', 6)
print e
#>> Employee(name='noname01', age=6)

# create Employee from dictionary
d = {'name': 'noname02', 'age': 7}
e = Employee(**d)
print e
#>> Employee(name='noname02', age=7)
print e._asdict()
#>> {'age': 7, 'name': 'noname02'}

Vous disposez d'une _asdict()méthode pour accéder à toutes les propriétés en tant que dictionnaire, mais vous ne pouvez pas ajouter d'attributs supplémentaires plus tard, uniquement pendant la construction.


6

dis par exemple

class A():
    def __init__(self):
        self.x=7
        self.y=8
        self.z="name"

si vous souhaitez définir les attributs à la fois

d = {'x':100,'y':300,'z':"blah"}
a = A()
a.__dict__.update(d)

1
vous pouvez utiliser des clés / valeurs pour plus de commodité:a.__dict__.update(x=100, y=300, z="blah")
simno

1

similaire à l'utilisation d'un dict, vous pouvez simplement utiliser des kwargs comme ceci:

class Person:
   def __init__(self, **kwargs):
       self.properties = kwargs

   def get_property(self, key):
       return self.properties.get(key, None)

   def main():
       timmy = Person(color = 'red')
       print(timmy.get_property('color')) #prints 'red'
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.