Uzyskujesz dostęp do kluczy dict jak atrybut?


303

Uważam, że wygodniej jest uzyskiwać dostęp do klawiszy dykta obj.foozamiast obj['foo'], więc napisałem ten fragment:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

Zakładam jednak, że musi istnieć jakiś powód, dla którego Python nie zapewnia tej funkcjonalności po wyjęciu z pudełka. Jakie byłyby ograniczenia i pułapki związane z dostępem do kluczy dict w ten sposób?


16
Jeśli uzyskujesz dostęp do kluczy zakodowanych na stałe z ograniczonego zestawu o stałym rozmiarze wszędzie, możesz lepiej tworzyć obiekty, które je przechowują. collections.namedtuplejest do tego bardzo przydatny.

6
stackoverflow.com/questions/3031219/... ma podobne rozwiązanie, ale idzie o krok dalej
keflavich,

1
Znaleziono moduł do tego na github.com/bcj/AttrDict . Nie wiem, jak to porównać z rozwiązaniami tutaj i w powiązanych pytaniach.
matt wilkie

Użyłem również podobnych hacków, teraz używameasydict.EasyDict
muon

Więcej sposobów uzyskiwania dostępu do członków słownika za pomocą „.” : stackoverflow.com/questions/2352181/…
Jasnoniebieska kropka

Odpowiedzi:


304

Najlepszym sposobem na to jest:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

Niektóre zalety:

  • To naprawdę działa!
  • Żadne metody klas słownikowych nie są śledzone (np .keys(). Działają dobrze. Chyba że - oczywiście - przypisujesz im jakąś wartość, patrz poniżej)
  • Atrybuty i elementy są zawsze zsynchronizowane
  • Próba uzyskania dostępu do nieistniejącego klucza jako atrybutu poprawnie podnosi AttributeErrorzamiastKeyError

Cons:

  • Takie metody nie.keys() będą działać dobrze, jeśli zostaną nadpisane przez przychodzące dane
  • Powoduje wyciek pamięci w Pythonie <2.7.4 / Python3 <3.2.3
  • Pylint idzie na banany E1123(unexpected-keyword-arg)iE1103(maybe-no-member)
  • Dla niewtajemniczonych wydaje się to czystą magią.

Krótkie wyjaśnienie, jak to działa

  • Wszystkie obiekty Pythona wewnętrznie przechowują swoje atrybuty w nazwie słownika __dict__.
  • Nie ma wymogu, aby słownik wewnętrzny __dict__musiał być „zwykłym słownikiem ”, więc możemy przypisać dowolną podklasę dict()do słownika wewnętrznego.
  • W naszym przypadku po prostu przypisujemy AttrDict()instancję, którą tworzymy (jak jesteśmy __init__).
  • Wywołując super()„s __init__()sposób możemy upewnić się, że to (już) zachowuje się dokładnie tak samo jak w słowniku, ponieważ funkcja zwraca cały słownik instancji kod.

Jednym z powodów, dla których Python nie zapewnia tej funkcjonalności po wyjęciu z pudełka

Jak zauważono na liście „wad”, łączy to przestrzeń nazw przechowywanych kluczy (które mogą pochodzić z dowolnych i / lub niezaufanych danych!) Z przestrzenią nazw wbudowanych atrybutów metody dict. Na przykład:

d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items():    # TypeError: 'list' object is not callable
    print "Never reached!"

1
Czy uważasz, że przeciek memorry nastąpiłoby z prostego obiektu takich jak: >>> klasy MYD (Object): ... def startowych __ (self, d): ... self .__ dict = D
Rafe

Powoduje wyciek nawet w 2,7
pi.

1
Zrób to <= 2.7.3, ponieważ tego właśnie używam.
pi.

1
W informacjach o wersji 2.7.4 wspominają o naprawieniu (nie wcześniej).
Robert Siemer

1
@ viveksinghggits tylko dlatego, że uzyskujesz dostęp do rzeczy przez ., nie możesz łamać reguł języka :) I nie chciałbym AttrDictautomatycznie przekształcać pól zawierających spacje w coś innego.
Yurik

125

Jeśli używasz notacji tablicowej, możesz mieć wszystkie legalne ciągi znaków jako część klucza. Na przykład,obj['!#$%^&*()_']


1
@Izkata tak. zabawne jest to, że w SE zwykle pojawia się „najważniejsze pytanie”, tj. tytuł i „dolne pytanie”, być może dlatego, że SE nie lubi słyszeć „tytuł mówi wszystko”; „zastrzeżenia” są tutaj najważniejsze.
n611x007

2
JavaScript nie jest szczególnie dobrym przykładem języka programowania, ale obiekty w JS obsługują zarówno dostęp do atrybutów, jak i notację tablicową, co zapewnia wygodę w typowym przypadku i ogólny powrót do symboli, które nie są dozwolonymi nazwami atrybutów.
André Caron

@Izkata Jak to odpowiada na pytanie. Ta odpowiedź mówi tylko, że klucze mogą mieć dowolne nazwy.
Melab

4
@Melab Pytanie brzmi What would be the caveats and pitfalls of accessing dict keys in this manner?(jako atrybuty), a odpowiedź brzmi: większość przedstawionych tutaj znaków nie będzie użyteczna.
Izkata

83

Z tego drugiego pytania SO jest świetny przykład implementacji, który upraszcza twój istniejący kod. Co powiesz na:

class AttributeDict(dict): 
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__

Znacznie bardziej zwięzły i nie pozostawia miejsca na dodatkowe cruft dostające się do ciebie __getattr__i __setattr__funkcjonuje w przyszłości.


Czy byłbyś w stanie wywołać AttributeDict.update lub AttributeDict.get przy użyciu tej metody?
Dor

13
Należy pamiętać, że jeśli dodasz nowe atrybuty w czasie wykonywania, nie zostaną one dodane do samego dykta, ale do atrybutu dict . Np d = AttributeDict(foo=1). d.bar = 1atrybut bar jest przechowywany w atrybucie dict, ale nie w samym dict. drukowanie dpokazuje tylko element foo.
P3trus

7
+1, ponieważ o ile mi wiadomo, działa idealnie. @GringoSuave, @Izkata, @ P3trus Proszę wszystkich, którzy twierdzą, że to się nie udaje, pokaż przykład, który nie działa d = AttributeDict(foo=1);d.bar = 1;print d=> {'foo': 1, 'bar': 1}Działa dla mnie!
Dave Abrahams,

4
@DaveAbrahams Przeczytaj pełne pytanie i spójrz na odpowiedzi Hery'ego, Ryana i TheCommunistDuck. Nie pyta o to, jak to zrobić, ale o problemy, które mogą się pojawić .
Izkata

6
Powinieneś podać __getattr__metodę, która podnosi, AttributeErrorjeśli dany atrybut nie istnieje, w przeciwnym razie rzeczy takie jak getattr(obj, attr, default_value)nie działają (tzn. Nie zwracają się, default_valuejeśli attrnie istnieją obj)
jcdude

83

Gdzie odpowiadam na zadane pytanie

Dlaczego Python nie oferuje tego po wyjęciu z pudełka?

Podejrzewam, że ma to związek z Zen Pythona : „Powinien istnieć jeden - a najlepiej tylko jeden - oczywisty sposób”. Stworzyłoby to dwa oczywiste sposoby dostępu do wartości ze słowników: obj['key']i obj.key.

Ostrzeżenia i pułapki

Należą do nich możliwy brak jasności i zamieszanie w kodzie. to znaczy, że poniższe informacje mogą być mylące dla kogoś , kto zamierza zachować kod w późniejszym terminie, a nawet dla ciebie, jeśli nie wrócisz do niego przez jakiś czas. Znów z Zen : „Czytelność się liczy!”

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1

Jeśli dzostanie utworzona instancja lub KEY zostanie zdefiniowana lub d[KEY] zostanie przypisana z dala od miejsca, w którym d.spamjest używana, może łatwo prowadzić do nieporozumień co do tego, co się dzieje, ponieważ nie jest to często używany idiom. Wiem, że mogłoby to mnie pomylić.

Dodatkowo, jeśli zmienisz wartość w KEYnastępujący sposób (ale przegapisz zmianę d.spam), otrzymasz teraz:

>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'

IMO, nie warte wysiłku.

Inne przedmioty

Jak zauważyli inni, możesz użyć dowolnego obiektu haszującego (nie tylko łańcucha) jako klucza dyktafonu. Na przykład,

>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>> 

jest legalne, ale

>>> C = type('C', (object,), {(2, 3): True})
>>> d = C()
>>> assert d.(2, 3) is True
  File "<stdin>", line 1
  d.(2, 3)
    ^
SyntaxError: invalid syntax
>>> getattr(d, (2, 3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
>>> 

nie jest. Daje to dostęp do całego zakresu drukowalnych znaków lub innych obiektów możliwych do skrótu dla kluczy słownika, których nie masz podczas uzyskiwania dostępu do atrybutu obiektu. Umożliwia to taką magię jak metaklasa obiektu w pamięci podręcznej, jak przepis z Python Cookbook (rozdz. 9) .

W którym redaguję

Wolę estetykę spam.eggsponad spam['eggs'](myślę, że wygląda na czystszą) i naprawdę zacząłem pragnąć tej funkcjonalności, kiedy ją spotkałem namedtuple. Ale wygoda bycia w stanie wykonać następujące atuty.

>>> KEYS = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>

Jest to prosty przykład, ale często używam dyktatów w różnych sytuacjach niż w obj.keynotacji (tj. Kiedy muszę czytać prefiksy z pliku XML). W innych przypadkach, gdy kusi mnie tworzenie instancji klasy dynamicznej i klepanie w nią niektórych atrybutów ze względów estetycznych, nadal używam dykta dla zachowania spójności w celu zwiększenia czytelności.

Jestem pewien, że OP już dawno to rozwiązał, ale jeśli nadal chce tę funkcjonalność, sugeruję pobranie jednego z pakietów, które ją udostępnia:

  • Wiązka jest tą, którą znam bardziej. Podklasadict, więc masz całą tę funkcjonalność.
  • AttrDict również wygląda na całkiem niezły, ale nie znam go tak dobrze i nie przeglądałem źródła tak szczegółowo, jak mam Bunch .
  • Addict jest aktywnie utrzymywany i zapewnia dostęp podobny do attr i więcej.
  • Jak zauważono w komentarzach Rotaretiego, Bunch jest przestarzały, ale istnieje aktywny widelec o nazwie Munch .

Jednak w celu poprawy czytelności jego kodu zdecydowanie zalecam, aby nie mieszał swoich stylów notacji. Jeśli woli ten zapis, powinien po prostu utworzyć obiekt dynamiczny, dodać do niego pożądane atrybuty i nazwać go dniem:

>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}


W czym aktualizuję, aby odpowiedzieć na pytanie uzupełniające w komentarzach

W komentarzach (poniżej) Elmo pyta:

Co jeśli chcesz zejść głębiej? (w odniesieniu do typu (...))

Chociaż nigdy nie użyłem tego przypadku użycia (ponownie, zwykle dictdla zachowania spójności używam zagnieżdżenia ), działa następujący kod:

>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
...     setattr(d, x, C())
...     i = 1
...     for y in 'one two three'.split():
...         setattr(getattr(d, x), y, i)
...         i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}

1
Pęczek jest przestarzały, ale jest na niego aktywny widelec: github.com/Infinidat/munch
Rotareti

@Rotareti - Dziękujemy za zgłoszenie się! Nie używam tej funkcji, więc nie wiedziałem o tym.
Doug R.

Co jeśli chcesz zejść głębiej? (w odniesieniu do typu (...))
Ole Aldric

6
Python jest jak odwrócony parasol trzymany wysoko podczas ulewnego deszczu. Na początku wszystko wygląda elegancko i funky, po jakimś czasie zaczyna się robić ciężkie, a potem nagle czytasz jakieś wbudowane guru w SE i wszystko wraca z całym ładunkiem na ramiona. Podczas gdy nadal jesteś mokry, czujesz się lżejszy, a wszystko jest tak czyste i odświeżone.
Ole Aldric


19

Możesz pobrać wygodną klasę kontenera ze standardowej biblioteki:

from argparse import Namespace

aby uniknąć konieczności kopiowania bitów kodu. Brak standardowego dostępu do słownika, ale łatwo go odzyskać, jeśli naprawdę chcesz. Kod w argparse jest prosty,

class Namespace(_AttributeHolder):
    """Simple object for storing attributes.

    Implements equality by attribute names and values, and provides a simple
    string representation.
    """

    def __init__(self, **kwargs):
        for name in kwargs:
            setattr(self, name, kwargs[name])

    __hash__ = None

    def __eq__(self, other):
        return vars(self) == vars(other)

    def __ne__(self, other):
        return not (self == other)

    def __contains__(self, key):
        return key in self.__dict__

2
PLUS 1 za odniesienie do standardowej biblioteki, która odnosi się do pierwszego komentarza PO.
Gordon Bean

4
Python zawiera w tym przypadku szybszą klasę (zaimplementowaną w C): types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André

18

Co jeśli chciałbyś klucza, który był metodą, taką jak __eq__lub __getattr__?

I nie byłbyś w stanie mieć wpisu, który nie zaczynał się na literę, więc używanie 0343853jako klucza jest niemożliwe .

A co jeśli nie chcesz używać łańcucha?


Rzeczywiście, lub na przykład inne przedmioty jako klucze. Jednak klasyfikowałbym ten błąd jako „oczekiwane zachowanie” - moim pytaniem bardziej dążyłem do nieoczekiwanego.
Izz ad-Din Ruhulessin

pickle.dumpwykorzystuje__getstate__
Cees Timmerman

12

krotek można użyć klawiszy dict. Jak uzyskasz dostęp do krotki w swojej konstrukcji?

Ponadto namedtuple jest wygodną strukturą, która może dostarczać wartości poprzez dostęp do atrybutów.


7
Wadą imienników jest to, że są niezmienne.
Izz ad-Din Ruhulessin

10
Niektórzy twierdzą, że niezmienność nie jest błędem, ale cechą krotek.
ben autor

9

Co powiesz na Prodict , małą klasę Python, którą napisałem, by rządzić nimi wszystkimi :)

Dodatkowo otrzymujesz automatyczne uzupełnianie kodu , rekurencyjne tworzenie instancji obiektów i automatyczną konwersję typów !

Możesz zrobić dokładnie to, o co prosiłeś:

p = Prodict()
p.foo = 1
p.bar = "baz"

Przykład 1: Podpowiedź do tekstu

class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

automatyczny kod zakończony

Przykład 2: Automatyczna konwersja typu

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])

print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'>

print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.flag_colors))  # <class 'list'>

2
instaluje się na python2 przez pip, ale nie działa na python2
Ant6n

2
@ Ant6n wymaga Pythona 3.6+ ze względu na adnotacje typu
Ramazan Polat

8

Nie działa ogólnie. Nie wszystkie prawidłowe klucze dict tworzą atrybuty adresowalne („klucz”). Musisz więc uważać.

Obiekty Pythona to w zasadzie słowniki. Wątpię więc, czy jest dużo wyników lub innych kar.


8

To nie odnosi się do pierwotnego pytania, ale powinno być przydatne dla osób, które, jak ja, kończą tutaj, szukając biblioteki zapewniającej tę funkcjonalność.

Addict to świetna biblioteka do tego: https://github.com/mewwts/addict rozwiązuje wiele problemów wymienionych w poprzednich odpowiedziach.

Przykład z dokumentów:

body = {
    'query': {
        'filtered': {
            'query': {
                'match': {'description': 'addictive'}
            },
            'filter': {
                'term': {'created_by': 'Mats'}
            }
        }
    }
}

Z uzależnionym:

from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'

8

Zastanawiałem się, jaki jest obecny stan „kluczy dyktujących jako attr” w ekosystemie Pythona. Jak zauważyło kilku komentujących, prawdopodobnie nie jest to coś, co chcesz rzucić od zera , ponieważ istnieje kilka pułapek i pistoletów, niektóre z nich bardzo subtelne. Poza tym nie polecałbym używania Namespacejako klasy podstawowej, byłem na tej drodze, to nie jest ładne.

Na szczęście istnieje kilka pakietów open source zapewniających tę funkcjonalność, gotowych do instalacji w trybie pip! Niestety istnieje kilka pakietów. Oto streszczenie z grudnia 2019 r.

Uczestnicy (najnowsze zobowiązania do opanowania | #commits | # contribs | pokrycie%):

Nieobsługiwany lub niedostatecznie konserwowany:

  • treedict (28.03.2014 | 95 | 2 |?%)
  • pęczek (2012-03-12 | 20 | 2 |?%)
  • NeoBunch

Obecnie polecam muncha lub uzależnia . Mają najwięcej zatwierdzeń, autorów i wydań, sugerując dla nich zdrową bazę kodu open source. Mają najczystszy readme.md, 100% zasięgu i dobrze wyglądający zestaw testów.

Nie mam psa w tym wyścigu (na razie!), Poza tym, że stworzyłem własny kod dict / attr i zmarnowałem mnóstwo czasu, ponieważ nie znałem wszystkich tych opcji :). Mogę przyczynić się do uzależnienia / chrupania w przyszłości, ponieważ wolałbym zobaczyć jedną solidną paczkę niż kilka rozdrobnionych. Jeśli ci się podobają, wnieś swój wkład! W szczególności wygląda na to, że Munch mógłby użyć znaczka codecov, a uzależniony może użyć znaczka wersji Python.

uzależnieni profesjonaliści:

  • rekurencyjna inicjalizacja (foo.abc = 'bar'), argumenty podobne do dict stają się uzależniające

uzależniony od:

  • cienie, typing.Dictjeśli tyfrom addict import Dict
  • Bez sprawdzania klucza. Ze względu na zezwolenie na rekurencyjne inicjowanie, jeśli źle wpisujesz klucz, po prostu tworzysz nowy atrybut, a nie KeyError (dzięki AljoSt)

munch plusy:

  • unikalne nazewnictwo
  • wbudowane funkcje ser / de dla JSON i YAML

Munch Cons:

  • żadna rekurencyjna inicjacja / nie może inicjować jednego atra naraz

W którym redaguję

Wiele księżyców temu, kiedy korzystałem z edytorów tekstu do pisania Pythona, w projektach z samym sobą lub innym twórcą, spodobał mi się styl dict-attrs, możliwość wstawiania kluczy po prostu zadeklarując foo.bar.spam = eggs. Teraz pracuję w zespołach i do wszystkiego używam IDE. Odszedłem od tego rodzaju struktur danych i ogólnie dynamicznego pisania na rzecz analizy statycznej, technik funkcjonalnych i wskazówek dotyczących typów. Zacząłem eksperymentować z tą techniką, dzieląc Pstruct na obiekty mojego własnego projektu:

class  BasePstruct(dict):
    def __getattr__(self, name):
        if name in self.__slots__:
            return self[name]
        return self.__getattribute__(name)

    def __setattr__(self, key, value):
        if key in self.__slots__:
            self[key] = value
            return
        if key in type(self).__dict__:
            self[key] = value
            return
        raise AttributeError(
            "type object '{}' has no attribute '{}'".format(type(self).__name__, key))


class FooPstruct(BasePstruct):
    __slots__ = ['foo', 'bar']

Daje to obiekt, który nadal zachowuje się jak dyktando, ale umożliwia także dostęp do kluczy takich jak atrybuty, w znacznie bardziej sztywny sposób. Zaletą jest to, że ja (lub nieszczęsni konsumenci twojego kodu) dokładnie wiem, jakie pola mogą istnieć, a jakie nie, a IDE może automatycznie uzupełniać pola. Również podklasowanie wanilii dictoznacza, że ​​serializacja Json jest łatwa. Myślę, że następną ewolucją tego pomysłu byłby niestandardowy generator protobufów, który emituje te interfejsy, a miłą konsekwencją jest uzyskanie międzygranicznych struktur danych i IPC za pośrednictwem gRPC za darmo.

Jeśli zdecydujesz się na dyktowanie, ważne jest, aby udokumentować, jakie pola są oczekiwane, dla własnego zdrowia psychicznego (i członków drużyny).

Edytuj / aktualizuj ten post, aby był aktualny!


2
dużą wadą addictjest to, że nie podniesie wyjątków, gdy źle Dictprzeliterujesz atrybut, ponieważ zwróci nowy (jest to konieczne, aby foo.abc = „bar” działał).
AljoSt

5

Oto krótki przykład niezmiennych zapisów wykorzystujących wbudowane collections.namedtuple:

def record(name, d):
    return namedtuple(name, d.keys())(**d)

i przykład użycia:

rec = record('Model', {
    'train_op': train_op,
    'loss': loss,
})

print rec.loss(..)

5

Aby urozmaicić odpowiedź, sci-kit learn zaimplementował to jako Bunch:

class Bunch(dict):                                                              
    """ Scikit Learn's container object                                         

    Dictionary-like object that exposes its keys as attributes.                 
    >>> b = Bunch(a=1, b=2)                                                     
    >>> b['b']                                                                  
    2                                                                           
    >>> b.b                                                                     
    2                                                                           
    >>> b.c = 6                                                                 
    >>> b['c']                                                                  
    6                                                                           
    """                                                                         

    def __init__(self, **kwargs):                                               
        super(Bunch, self).__init__(kwargs)                                     

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

    def __dir__(self):                                                          
        return self.keys()                                                      

    def __getattr__(self, key):                                                 
        try:                                                                    
            return self[key]                                                    
        except KeyError:                                                        
            raise AttributeError(key)                                           

    def __setstate__(self, state):                                              
        pass                       

Wszystko, czego potrzebujesz, to zdobyć metody setattri getattr- getattrsprawdzenie kluczy dyktafów i przejście do sprawdzania rzeczywistych atrybutów. Jest setstaetto poprawka do usuwania / paczkowania „pęczków” - w razie zainteresowania sprawdź https://github.com/scikit-learn/scikit-learn/issues/6196


3

Nie trzeba pisać własnego, ponieważ setattr () i getattr () już istnieją.

Przewaga obiektów klasowych prawdopodobnie odgrywa rolę w definiowaniu i dziedziczeniu klas.


3

Stworzyłem to na podstawie danych wejściowych z tego wątku. Muszę jednak użyć odict, więc musiałem zastąpić get i ustawić attr. Myślę, że powinno to działać w przypadku większości zastosowań specjalnych.

Sposób użycia wygląda następująco:

# Create an ordered dict normally...
>>> od = OrderedAttrDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od
OrderedAttrDict([('a', 1), ('b', 2)])

# Get and set data using attribute access...
>>> od.a
1
>>> od.b = 20
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])

# Setting a NEW attribute only creates it on the instance, not the dict...
>>> od.c = 8
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
>>> od.c
8

Klasa:

class OrderedAttrDict(odict.OrderedDict):
    """
    Constructs an odict.OrderedDict with attribute access to data.

    Setting a NEW attribute only creates it on the instance, not the dict.
    Setting an attribute that is a key in the data will set the dict data but 
    will not create a new instance attribute
    """
    def __getattr__(self, attr):
        """
        Try to get the data. If attr is not a key, fall-back and get the attr
        """
        if self.has_key(attr):
            return super(OrderedAttrDict, self).__getitem__(attr)
        else:
            return super(OrderedAttrDict, self).__getattr__(attr)


    def __setattr__(self, attr, value):
        """
        Try to set the data. If attr is not a key, fall-back and set the attr
        """
        if self.has_key(attr):
            super(OrderedAttrDict, self).__setitem__(attr, value)
        else:
            super(OrderedAttrDict, self).__setattr__(attr, value)

Jest to całkiem fajny wzorzec już wspomniany w wątku, ale jeśli chcesz tylko zrobić dykt i przekonwertować go na obiekt, który działa z funkcją autouzupełniania w środowisku IDE itp .:

class ObjectFromDict(object):
    def __init__(self, d):
        self.__dict__ = d


3

Tego używam

args = {
        'batch_size': 32,
        'workers': 4,
        'train_dir': 'train',
        'val_dir': 'val',
        'lr': 1e-3,
        'momentum': 0.9,
        'weight_decay': 1e-4
    }
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)

print (args.lr)

To dobra szybka i brudna odpowiedź. Moim jedynym spostrzeżeniem / komentarzem jest to, że myślę, że konstruktor namedtuple zaakceptuje listę ciągów, więc twoje rozwiązanie można uprościć (tak myślę) do:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen

2

Możesz to zrobić, korzystając z klasy, którą właśnie stworzyłem. Dzięki tej klasie możesz używać Mapobiektu jako innego słownika (w tym serializacji json) lub z notacją kropkową. Mam nadzieję, że ci pomogę:

class Map(dict):
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self[k] = v

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Przykłady użycia:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']

1
Zauważ, że potrafi ukrywać dictmetody, np .: m=Map(); m["keys"] = 42; m.keys()daje TypeError: 'int' object is not callable.
bfontaine

@bfontaine Chodzi o to, aby być rodzajem, field/attributea nie a method, ale jeśli przypiszesz metodę zamiast liczby, możesz uzyskać do niej dostęp m.method().
epool

2

Pozwól, że opublikuję kolejną implementację, która opiera się na odpowiedzi Kinvais, ale integruje pomysły z AttributeDict zaproponowane w http://databio.org/posts/python_AttributeDict.html .

Zaletą tej wersji jest to, że działa ona również dla zagnieżdżonych słowników:

class AttrDict(dict):
    """
    A class to convert a nested Dictionary into an object with key-values
    that are accessible using attribute notation (AttrDict.attribute) instead of
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
    """

    # Inspired by:
    # http://stackoverflow.com/a/14620633/1551810
    # http://databio.org/posts/python_AttributeDict.html

    def __init__(self, iterable, **kwargs):
        super(AttrDict, self).__init__(iterable, **kwargs)
        for key, value in iterable.items():
            if isinstance(value, dict):
                self.__dict__[key] = AttrDict(value)
            else:
                self.__dict__[key] = value

1
class AttrDict(dict):

     def __init__(self):
           self.__dict__ = self

if __name__ == '____main__':

     d = AttrDict()
     d['ray'] = 'hope'
     d.sun = 'shine'  >>> Now we can use this . notation
     print d['ray']
     print d.sun

1

Rozwiązaniem jest:

DICT_RESERVED_KEYS = vars(dict).keys()


class SmartDict(dict):
    """
    A Dict which is accessible via attribute dot notation
    """
    def __init__(self, *args, **kwargs):
        """
        :param args: multiple dicts ({}, {}, ..)
        :param kwargs: arbitrary keys='value'

        If ``keyerror=False`` is passed then not found attributes will
        always return None.
        """
        super(SmartDict, self).__init__()
        self['__keyerror'] = kwargs.pop('keyerror', True)
        [self.update(arg) for arg in args if isinstance(arg, dict)]
        self.update(kwargs)

    def __getattr__(self, attr):
        if attr not in DICT_RESERVED_KEYS:
            if self['__keyerror']:
                return self[attr]
            else:
                return self.get(attr)
        return getattr(self, attr)

    def __setattr__(self, key, value):
        if key in DICT_RESERVED_KEYS:
            raise AttributeError("You cannot set a reserved name as attribute")
        self.__setitem__(key, value)

    def __copy__(self):
        return self.__class__(self)

    def copy(self):
        return self.__copy__()

1

Jakie byłyby ograniczenia i pułapki związane z dostępem do kluczy dict w ten sposób?

Jak sugeruje @Henry, jednym z powodów, dla których dostęp kropkowany nie może być używany w dyktach, jest to, że ogranicza nazwy kluczy dykt do zmiennych poprawnych w języku python, ograniczając w ten sposób wszystkie możliwe nazwy.

Poniżej podano przykłady, dlaczego dostęp punktowy nie byłby ogólnie pomocny, biorąc pod uwagę dyktando, d :

Ważność

Następujące atrybuty byłyby nieprawidłowe w Pythonie:

d.1_foo                           # enumerated names
d./bar                            # path names
d.21.7, d.12:30                   # decimals, time
d.""                              # empty strings
d.john doe, d.denny's             # spaces, misc punctuation 
d.3 * x                           # expressions  

Styl

Konwencje PEP8 nakładałyby miękkie ograniczenie na nazewnictwo atrybutów:

A. Zarezerwowane nazwy słów kluczowych (lub funkcji wbudowanych):

d.in
d.False, d.True
d.max, d.min
d.sum
d.id

Jeśli nazwa argumentu funkcji koliduje z zarezerwowanym słowem kluczowym, zwykle lepiej jest dołączyć pojedynczy znak podkreślenia ...

B. Reguła dotycząca nazw metod i zmiennych :

Nazwy zmiennych są zgodne z tą samą konwencją co nazwy funkcji.

d.Firstname
d.Country

Użyj reguł nazewnictwa funkcji: małe litery ze słowami oddzielonymi podkreślnikami, jeśli to konieczne, aby poprawić czytelność.


Czasami te obawy pojawiają się w bibliotekach takich jak pandy , które umożliwiają kropkowany dostęp do kolumn DataFrame według nazw. Domyślnym mechanizmem rozwiązywania ograniczeń nazewnictwa jest także notacja tablicowa - ciąg znaków w nawiasach.

Jeśli te ograniczenia nie dotyczą twojego przypadku użycia, istnieje kilka opcji dotyczących struktur danych z dostępem punktowym .



1

To nie jest „dobra” odpowiedź, ale pomyślałem, że to fajne (nie obsługuje zagnieżdżonych dykt w obecnej formie). Po prostu zawiń swój dykt w funkcję:

def make_funcdict(d=None, **kwargs)
    def funcdict(d=None, **kwargs):
        if d is not None:
            funcdict.__dict__.update(d)
        funcdict.__dict__.update(kwargs)
        return funcdict.__dict__
    funcdict(d, **kwargs)
    return funcdict

Teraz masz nieco inną składnię. Aby uzyskać dostęp do elementów nagrania tak, jak robią to atrybuty f.key. Aby uzyskać dostęp do elementów dict (i innych metod dict) w zwykły sposób, f()['key']możemy i wygodnie zaktualizować dict, wywołując f z argumentami słów kluczowych i / lub słownikiem

Przykład

d = {'name':'Henry', 'age':31}
d = make_funcdict(d)
>>> for key in d():
...     print key
... 
age
name
>>> print d.name
... Henry
>>> print d.age
... 31
>>> d({'Height':'5-11'}, Job='Carpenter')
... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'}

I oto jest. Będę szczęśliwy, jeśli ktoś zasugeruje zalety i wady tej metody.


0

Jak zauważył Doug, istnieje pakiet Bunch, którego można użyć do uzyskania obj.keyfunkcjonalności. W rzeczywistości istnieje nowsza wersja o nazwie

NeoBunch

Ma jednak świetną funkcję konwersji słownika do obiektu NeoBunch za pomocą funkcji neobunchify . Często używam szablonów Mako i przekazywanie danych, ponieważ obiekty NeoBunch sprawiają, że są one znacznie bardziej czytelne, więc jeśli zdarzy się, że użyjesz normalnego dykta w swoim programie Python, ale chcesz notacji kropkowej w szablonie Mako, możesz użyć tego w ten sposób:

from mako.template import Template
from neobunch import neobunchify

mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
    out_file.write(mako_template.render(**neobunchify(data)))

Szablon Mako mógłby wyglądać następująco:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor

Link do NeoBuncha to 404
DeusXMachina,

0

Najłatwiej jest zdefiniować klasę, nazwijmy ją Przestrzeń nazw. , który korzysta z obiektu dict .update () na dykcie. Następnie dykt będzie traktowany jak obiekt.

class Namespace(object):
    '''
    helps referencing object in a dictionary as dict.key instead of dict['key']
    '''
    def __init__(self, adict):
        self.__dict__.update(adict)



Person = Namespace({'name': 'ahmed',
                     'age': 30}) #--> added for edge_cls


print(Person.name)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.