Ustaw atrybuty ze słownika w Pythonie

102

Czy można stworzyć obiekt ze słownika w Pythonie w taki sposób, że każdy klucz jest atrybutem tego obiektu?

Coś takiego:

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

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

Myślę, że byłoby to odwrotnością tego pytania: słownik Pythona z pól obiektu

OscarRyz
źródło

Odpowiedzi:

172

Jasne, coś takiego:

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

Aktualizacja

Jak sugeruje Brent Nash, możesz uczynić to bardziej elastycznym, zezwalając również na argumenty słów kluczowych:

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

Następnie możesz to nazwać tak:

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

lub tak:

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

lub nawet tak:

employee_template = {"role": "minion"}
e = Employee(employee_template, name="abc", age=32)
Ian Clelland
źródło
4
Jeśli przekażesz początkowe dane, gdy def __init__(self,**initial_data)uzyskasz dodatkową korzyść z posiadania metody init, która może również obsługiwać argumenty słów kluczowych (np. „E = Pracownik (imię = 'Oscar')” lub po prostu weźmiesz słownik (np. „E = Pracownik ( ** dict) ”).
Brent pisze kod
2
Oferowanie zarówno interfejsów API, jak Employee(some_dict)i Employee(**some_dict)interfejsów API jest niespójne. Powinno być dostarczone, co jest lepsze.
Mike Graham
4
Jeśli domyślne dla ARG za ustawić aby ()zamiast None, można zrobić to tak: def __init__(self, iterable=(), **kwargs): self.__dict__.update(iterable, **kwargs).
Matt Anderson,
4
Wiem, że to stare pytanie, ale chcę tylko dodać, że można to zrobić w dwóch wierszach ze zrozumieniem listy, na przykład:[[setattr(self,key,d[key]) for key in d] for d in some_dict]
TZ
2
Bardzo chciałbym wiedzieć, dlaczego nie jest to wbudowane w Python w jakiś prosty sposób. Używam tego, aby poradzić sobie z rzucaniem przez Pythona GeoIP2 API AddressNotFoundError(aby zwrócić poprawione dane w tym przypadku, zamiast wysadzać) - uznałem za szalone, że coś tak prostego w PHP ( (object) ['x' => 'y']) wymagało tak dużo okrucieństwa w Pythonie
Someguy123
47

Ustawianie atrybutów w ten sposób prawie na pewno nie jest najlepszym sposobem rozwiązania problemu. Zarówno:

  1. Wiesz, jakie wszystkie pola powinny być przed czasem. W takim przypadku możesz jawnie ustawić wszystkie atrybuty. To by wyglądało

    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 

    lub

  2. Nie wiesz, jakie pola powinny być przed czasem. W takim przypadku należy przechowywać dane jako dict, zamiast zanieczyszczać przestrzeń nazw obiektów. Atrybuty służą do dostępu statycznego. Tak by wyglądała ta sprawa

    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 

Innym rozwiązaniem, które jest w zasadzie równoważne z przypadkiem 1, jest użycie pliku collections.namedtuple . Zobacz odpowiedź van, aby dowiedzieć się, jak to zaimplementować.

Mike Graham
źródło
A co, jeśli scenariusz leży gdzieś pomiędzy twoimi dwoma skrajnościami? To jest dokładnie przypadek użycia w tym przypadku, a obecnie AFAICT nie ma sposobu, aby to zrobić w sposób SUCHY i Pythonowy.
DylanYoung
15

Możesz uzyskać dostęp do atrybutów obiektu za pomocą __dict__i wywołać na nim metodę update:

>>> 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
Dave Kirby
źródło
3
__dict__jest artefaktem implementacji i nie należy go używać. Ponadto ignoruje to istnienie deskryptorów w klasie.
Ignacio Vazquez-Abrams
1
@Ignacio co masz na myśli mówiąc o „artefakcie implementacji”? Czego nie powinniśmy być tego świadomi? Albo że może nie być obecny na różnych platformach? (np. Python w systemie Windows kontra Python w systemie Linux) Jaka byłaby akceptowalna odpowiedź?
OscarRyz
12
__dict__jest udokumentowaną częścią języka, a nie artefaktem implementacji.
Dave Kirby,
3
Używanie setattrjest lepsze od __dict__bezpośredniego dostępu . Musisz pamiętać o wielu rzeczach, które mogą prowadzić do __dict__nieobecności lub nie robienia tego, co chcesz, kiedy używasz __dict__, ale setattrsą praktycznie identyczne z faktycznym robieniem foo.bar = baz.
Mike Graham
1
@DaveKirby: Wydaje się, że ogólne użycie __dict__odradza się: docs.python.org/tutorial/classes.html#id2
equaeghe
11

Dlaczego nie używać nazw atrybutów jako kluczy do słownika?

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

Możesz zainicjować za pomocą nazwanych argumentów, listy krotek, słownika lub przypisania poszczególnych atrybutów, np .:

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)

Alternatywnie, zamiast zwiększać błąd atrybutu, możesz zwrócić None dla nieznanych wartości. (Sztuczka używana w klasie pamięci web2py)

RufusVS
źródło
7

Myślę, że użycie odpowiedzi settattrjest najlepszym rozwiązaniem, jeśli naprawdę potrzebujesz wsparcia dict.

Ale jeśli Employeeobiekt jest tylko strukturą, do której można uzyskać dostęp za pomocą składni kropki ( .name) zamiast składni dict ( ['name']), możesz użyć namedtuple w następujący sposób:

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'}

Masz _asdict()metodę dostępu do wszystkich właściwości jako słownika, ale nie możesz później dodać dodatkowych atrybutów, tylko podczas konstrukcji.

awangarda
źródło
6

powiedz na przykład

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

jeśli chcesz od razu ustawić atrybuty

d = {'x':100,'y':300,'z':"blah"}
a = A()
a.__dict__.update(d)
islam ezzat
źródło
1
dla wygody możesz użyć klucza / wartości:a.__dict__.update(x=100, y=300, z="blah")
simno
1

podobnie jak przy użyciu dict, możesz po prostu użyć kwargs w następujący sposób:

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'
Alex Spencer
źródło