Jak używać kropki „.” uzyskać dostęp do członków słownika?

282

Jak sprawić, by członkowie słownika Python byli dostępni przez kropkę „.”?

Na przykład, zamiast pisać mydict['val'], chciałbym pisać mydict.val.

Chciałbym również uzyskać dostęp do zagnieżdżonych nagrań w ten sposób. Na przykład

mydict.mydict2.val 

odnosi się do

mydict = { 'mydict2': { 'val': ... } }
bodacydo
źródło
20
Wiele sytuacji, w których ludzie używają zagnieżdżonych dykt, byłyby równie dobrze lub lepiej obsługiwane przez dykty z krotkami jako kluczami, gdzie d[a][b][c]są zastępowane przez d[a, b, c].
Mike Graham
7
To nie jest magia: foo = {}; foo [1,2,3] = „raz, dwa, trzy!”; foo.keys () => [(1,2,3)]
Bryan Oakley
10
Łał. Wow ponownie. Nie wiedziałem, że krotki mogą być kluczem do dyktowania. Wow trzeci raz.
bodacydo
3
Dowolny obiekt, który jest „mieszalny”, może być użyty jako klucz do nagrania. Większość niezmiennych obiektów również jest haszowalnych, ale tylko wtedy, gdy cała ich zawartość jest haszowalna. Kod d [1, 2, 3] działa, ponieważ „,” jest „utwórz operator krotki”; jest taki sam jak d [(1, 2, 3)]. Nawiasy są często opcjonalne wokół deklaracji krotki.
Larry Hastings
6
Czy rozważałeś przypadek, w którym klucz sam ma kropkę - {"my.key":"value"}? Lub gdy kluczem jest słowo kluczowe, na przykład „z”? Rozważyłem to kilka razy i to więcej problemów i rozwiązywania problemów niż spodziewanych korzyści.
Todor Minakov

Odpowiedzi:

147

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!'
# Or
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']
epool
źródło
21
Do pracy na Python 3 i zaktualizowanego .iteritems()do.items()
Berto
13
Zauważ, że będzie to zachowywać się inaczej niż zwykłe oczekiwania, ponieważ nie podniesie się, AttributeErrorjeśli atrybut nie istnieje. Zamiast tego wróci None.
mic_e
Zalecamy dodanie getstate i setstate , aby głębokie kopiowanie i inne systemy mogły go obsługiwać.
user1363990,
4
Możesz uprościć swojego konstruktora self.update(*args,**kwargs). Możesz także dodać __missing__(self,key): value=self[key]= type(self)(); return value. Następnie możesz dodać brakujące wpisy za pomocą notacji kropkowej. Jeśli chcesz, aby można go było odebrać, możesz dodać __getstate__i__setstate__
Jens Munk
1
To sprawiłoby, że hasattr(Map, 'anystring') is true. which means the hasattr would always return True due to overriding __getattr__`
Xiao
264

Zawsze trzymałem to w pliku użytkownika. Możesz używać go jako miksu na własnych zajęciach.

class dotdict(dict):
    """dot.notation access to dictionary attributes"""
    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

mydict = {'val':'it works'}
nested_dict = {'val':'nested works too'}
mydict = dotdict(mydict)
mydict.val
# 'it works'

mydict.nested = dotdict(nested_dict)
mydict.nested.val
# 'nested works too'
derek73
źródło
5
Bardzo prosta odpowiedź, świetnie! Czy zdarza ci się wiedzieć, co powinienem zrobić, aby mieć możliwość uzupełniania tabulatorów w IPython? Klasa musiałaby zaimplementować __dir __ (self), ale jakoś nie mogę zmusić go do działania.
andreas-h
8
+1 za prostotę. ale nie działa na zagnieżdżone dyktanda. d = {'foo': {'bar': 'baz'}}; d = dotdict(d); d.foo.barzgłasza błąd atrybutu, ale d.foodziała dobrze.
tmthyjames,
2
Tak, to nie działa w przypadku złożonych struktur zagnieżdżonych.
David
16
@tmthyjames możesz po prostu zwrócić obiekt typu dotdict w metodzie gettera, aby rekurencyjnie uzyskać dostęp do atrybutów z notacją kropkową, takich jak: python class DotDict(dict): """dot.notation access to dictionary attributes""" def __getattr__(*args): val = dict.get(*args) return DotDict(val) if type(val) is dict else val __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__
TMKasun
4
Po eksperymentowaniu z nim wydaje się getto naprawdę zły pomysł, ponieważ wróci Nonezamiast
zgłaszać
117

Zainstaluj dotmapprzezpip

pip install dotmap

Robi wszystko, co chcesz, i podklas dict, więc działa jak zwykły słownik:

from dotmap import DotMap

m = DotMap()
m.hello = 'world'
m.hello
m.hello += '!'
# m.hello and m['hello'] now both return 'world!'
m.val = 5
m.val2 = 'Sam'

Ponadto możesz przekonwertować go na dictobiekty:

d = m.toDict()
m = DotMap(d) # automatic conversion in constructor

Oznacza to, że jeśli coś, do czego chcesz uzyskać dostęp, jest już w dictformie, możesz zmienić je w DotMapłatwy dostęp:

import json
jsonDict = json.loads(text)
data = DotMap(jsonDict)
print data.location.city

Wreszcie automatycznie tworzy nowe DotMapwystąpienia potomne, dzięki czemu możesz wykonywać następujące czynności:

m = DotMap()
m.people.steve.age = 31

Porównanie do Buncha

Pełne ujawnienie: Jestem twórcą DotMap . Stworzyłem go, ponieważ Bunchbrakowało tych funkcji

  • zapamiętywanie elementów zamówienia jest dodawane i iterowanie w tej kolejności
  • automatyczne DotMaptworzenie potomków, które oszczędza czas i zapewnia czystszy kod, gdy masz dużo hierarchii
  • konstruowanie z dicti rekurencyjne konwertowanie wszystkich dictinstancji podrzędnych naDotMap
Chris Redford
źródło
2
:-) czy możesz sprawić, by działało to z klawiszami, które mają już kropkę w nazwie? {"test.foo": "bar"}można uzyskać dostęp za pośrednictwem mymap.test.fooTo byłoby fantastyczne. Przekształcenie płaskiej mapy w mapę głęboką zajmie pewną regresję, a następnie zastosowanie do niej DotMap, ale warto!
dlite922,
Schludny. W jakiś sposób sprawić, by zestawianie / uzupełnianie zakładek działało z kluczami w notatniku Jupyter? Dostęp w stylu kropkowym jest najbardziej cenny do użytku interaktywnego.
Dmitri
@Dmitri Cool produkt. Nigdy wcześniej o tym nie słyszałem, więc nie jestem pewien, jak sprawić, by autouzupełnianie działało. Zgadzam się, że używanie DotMapautouzupełniania działa najlepiej. Używam Sublime Text, który automatycznie uzupełnia wcześniej wpisane słowa kluczowe.
Chris Redford
1
Uważam, że brakuje ekstrakcji słownika dla rzeczy takich jak **kwargslub c = {**a, **b}. W rzeczywistości zawiesza się cicho, podczas rozpakowywania zachowuje się jak pusty słownik.
Simon Streicher
@ SimonStreicher Przetestowałem to m = DotMap(); m.a = 2; m.b = 3; print('{a} {b}'.format(**m));i spełniłem oczekiwania 2 3. Jeśli masz sprawdzoną zepsutą skrzynkę, która działa, dict()ale nie DotMap(), prześlij kod na kartę Problemy w GitHub.
Chris Redford
56

Wyprowadzić ze słownika i wdrożyć __getattr__i __setattr__.

Lub możesz użyć Bunch, który jest bardzo podobny.

Nie sądzę, że można monkeypatch wbudowaną klasę dict.

Kugel
źródło
2
Co dokładnie oznacza monkeypatch? Słyszałem o tym, ale nie wykorzystałem. (Przepraszam, że zadaję takie pytania dla początkujących, nie jestem jeszcze tak dobry w programowaniu (jestem tylko studentem drugiego roku))
bodacydo
9
Monkeypatching wykorzystuje dynamikę Pythona (lub innego języka) do zmiany czegoś, co zwykle byłoby zdefiniowane w kodzie źródłowym. Dotyczy to w szczególności zmiany definicji klas po ich utworzeniu.
Mike Graham
Jeśli często korzystasz z tej funkcji, uważaj na szybkość Buncha. Używałem go dość często i ostatecznie zużywałem jedną trzecią czasu mojego żądania. Sprawdź moją odpowiedź, aby uzyskać bardziej szczegółowe wyjaśnienie tego.
JayD3e
22

Materiał ma naprawdę ładne, minimalne wdrożenie . Rozszerzając to, aby umożliwić zagnieżdżony dostęp, możemy użyć a defaultdict, a wynik wygląda mniej więcej tak:

from collections import defaultdict

class AttributeDict(defaultdict):
    def __init__(self):
        super(AttributeDict, self).__init__(AttributeDict)

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

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

Skorzystaj z niego w następujący sposób:

keys = AttributeDict()
keys.abc.xyz.x = 123
keys.abc.xyz.a.b.c = 234

To trochę wyjaśnia odpowiedź Kugela: „czerp z dykta i zaimplementuj __getattr__i __setattr__”. Teraz wiesz jak!

Dave
źródło
1
Ten jest niesamowity!
Thomas Klinger
Miło jest dołączyć domyślny dykt - jednak wydaje się, że działa to tylko przy rozpoczynaniu dyktowania od zera. Jeśli musimy przekonwertować istniejący słownik na rekursywnie „dotdict”. Oto alternatywa, dotdictktóra pozwala konwertować istniejący dictobiekt rekurencyjnie: gist.github.com/miku/…
miku
19

Próbowałem tego:

class dotdict(dict):
    def __getattr__(self, name):
        return self[name]

możesz __getattribute__też spróbować .

spraw, aby każdy dyktat był typem dotdict, jeśli chcesz zainicjować to z wielowarstwowego dykta, spróbuj __init__również wdrożyć .

tdihp
źródło
oops, odpowiedź @ Kugela jest podobna.
tdihp
1
tdihp, wciąż podoba mi się twoja odpowiedź, ponieważ zrozumiałem ją szybciej - ma właściwy kod.
yigal
1
+1 za rzeczywisty kod. Ale sugestia @ Kugel dotycząca używania Buncha jest również bardzo dobra.
Dannid
Uznałem, że użyteczne jest osadzenie tego w funkcji poprzez umieszczenie def docdict(name):przed nią, a następnie `if isinstance (name, dict): return DotDict (name) return
name`
świetny prosty przykład .. Rozszerzyłem to trochę, aby zagnieżdżony dykt można było łatwo połączyć w łańcuch, podobnie jak @DanielMoskovich, ale również zwracał poprawnie węzły liści dla int, string itp. ... lub null, jeśli nie znalezionoclass dotdict(dict): def __getattr__(self, name): if name not in self: return None elif type(self[name]) is dict: return JsonDot(self[name]) else: return self[name]
D Sievers
11

Nie rób Dostęp do atrybutów i indeksowanie to osobne rzeczy w Pythonie i nie powinieneś chcieć, aby działały tak samo. Stwórz klasę (prawdopodobnie stworzoną przez namedtuple), jeśli masz coś, co powinno mieć dostępne atrybuty, i użyj []notacji, aby uzyskać przedmiot ze słownika.

Mike Graham
źródło
Dziękuję za odpowiedź. Ale spójrz na to pytanie, które właśnie zadałem: stackoverflow.com/questions/2352252/... To wydaje się dobrym pomysłem, aby użyć .zamiast []dostępu do skomplikowanych struktur danych w szablonach Mako.
bodacydo
2
Widzę dla tego przypadek użycia; w rzeczywistości zrobiłem to zaledwie kilka tygodni temu. W moim przypadku chciałem obiektu, do którego mógłbym uzyskać dostęp do atrybutów za pomocą notacji kropkowej. Stwierdziłem, że bardzo łatwo jest odziedziczyć po dict, więc mam wszystkie wbudowane funkcje dict, ale interfejs publiczny do tego obiektu używa notacji kropkowej (jest to zasadniczo interfejs tylko do odczytu dla niektórych danych statycznych). Moi użytkownicy są znacznie bardziej zadowoleni z „foo.bar” ​​niż z „foo [„ bar ”]” i cieszę się, że mogę korzystać z funkcji typu danych dict.
Bryan Oakley
10
Znasz już dobry styl Pythona: mówimy ci, nie udawaj, że wartości dykt są atrybutami. To zła praktyka. Na przykład, co jeśli chcesz zapisać wartość o takiej samej nazwie jak istniejący atrybut dykta, taki jak „items” lub „get” lub „pop”? Prawdopodobnie coś mylącego. Więc nie rób tego!
Larry Hastings
5
Ups, zapomniałem o atrybutach takich jak „itemy”, „get” lub „pop”. Dziękujemy za przedstawienie tego ważnego przykładu!
bodacydo
5
@ Gabe, minęło sporo czasu ... ale myślę, że warto powiedzieć. Nie jest „wystarczająco dobry w JS”: jest „wystarczająco okropny w JS”. To zabawne, gdy przechowujesz klucze / attr, które mają taką samą nazwę jak inne ważne atrybuty w łańcuchu prototypowym.
bgusach
11

Jeśli chcesz marynować zmodyfikowany słownik, musisz dodać kilka metod stanów do powyższych odpowiedzi:

class DotDict(dict):
    """dot.notation access to dictionary attributes"""
    def __getattr__(self, attr):
        return self.get(attr)
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

    def __getstate__(self):
        return self

    def __setstate__(self, state):
        self.update(state)
        self.__dict__ = self
Włodzimierz
źródło
Dzięki za komentarz na temat wytrawiania. Ten błąd doprowadził mnie do szału i zdałem sobie sprawę, że to z tego powodu!
Shagru
Dzieje się tak również podczas korzystania z copy.deepcopy. Ten dodatek jest potrzebny.
user1363990,
Uproszczenie:__getattr__ = dict.get
martineau
9

Opierając się na odpowiedzi Kugela i biorąc pod uwagę słowa ostrożności Mike'a Grahama, co zrobić, jeśli zrobimy opakowanie?

class DictWrap(object):
  """ Wrap an existing dict, or create a new one, and access with either dot 
    notation or key lookup.

    The attribute _data is reserved and stores the underlying dictionary.
    When using the += operator with create=True, the empty nested dict is 
    replaced with the operand, effectively creating a default dictionary
    of mixed types.

    args:
      d({}): Existing dict to wrap, an empty dict is created by default
      create(True): Create an empty, nested dict instead of raising a KeyError

    example:
      >>>dw = DictWrap({'pp':3})
      >>>dw.a.b += 2
      >>>dw.a.b += 2
      >>>dw.a['c'] += 'Hello'
      >>>dw.a['c'] += ' World'
      >>>dw.a.d
      >>>print dw._data
      {'a': {'c': 'Hello World', 'b': 4, 'd': {}}, 'pp': 3}

  """

  def __init__(self, d=None, create=True):
    if d is None:
      d = {}
    supr = super(DictWrap, self)  
    supr.__setattr__('_data', d)
    supr.__setattr__('__create', create)

  def __getattr__(self, name):
    try:
      value = self._data[name]
    except KeyError:
      if not super(DictWrap, self).__getattribute__('__create'):
        raise
      value = {}
      self._data[name] = value

    if hasattr(value, 'items'):
      create = super(DictWrap, self).__getattribute__('__create')
      return DictWrap(value, create)
    return value

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

  def __getitem__(self, key):
    try:
      value = self._data[key]
    except KeyError:
      if not super(DictWrap, self).__getattribute__('__create'):
        raise
      value = {}
      self._data[key] = value

    if hasattr(value, 'items'):
      create = super(DictWrap, self).__getattribute__('__create')
      return DictWrap(value, create)
    return value

  def __setitem__(self, key, value):
    self._data[key] = value

  def __iadd__(self, other):
    if self._data:
      raise TypeError("A Nested dict will only be replaced if it's empty")
    else:
      return other
MJ
źródło
8

Użyj SimpleNamespace:

>>> from types import SimpleNamespace   
>>> d = dict(x=[1, 2], y=['a', 'b'])
>>> ns = SimpleNamespace(**d)
>>> ns.x
[1, 2]
>>> ns
namespace(x=[1, 2], y=['a', 'b'])
Dmitrij Zotikov
źródło
1
To podejście działa lepiej. (z jsonem załadowanym z pliku)
ged
Czy to konto zagnieżdżonych nagrań?
Mojimi
1
Nie obsługuje zagnieżdżonego Dict. docs.python.org/3.3/library/types.html#types.SimpleNamespace
Carson
6

Lubię Muncha i daje on wiele przydatnych opcji poza dostępem do kropki.

import munch

temp_1 = {'person': {'fname': 'senthil', 'lname': 'ramalingam'}}

dict_munch = munch.munchify (temp_1)

dict_munch.person.fname

Senthil
źródło
6

Ostatnio natknąłem się na bibliotekę „ Box ”, która robi to samo.

Polecenie instalacji: pip install python-box

Przykład:

from box import Box

mydict = {"key1":{"v1":0.375,
                    "v2":0.625},
          "key2":0.125,
          }
mydict = Box(mydict)

print(mydict.key1.v1)

Przekonałem się, że jest bardziej skuteczny niż inne istniejące biblioteki, takie jak dotmap, które generują błąd rekurencji w Pythonie, gdy masz duże zagnieżdżone nagrania.

link do biblioteki i szczegóły: https://pypi.org/project/python-box/

Pradip Gupta
źródło
5

Użycie __getattr__, bardzo proste, działa w Pythonie 3.4.3

class myDict(dict):
    def __getattr__(self,val):
        return self[val]


blockBody=myDict()
blockBody['item1']=10000
blockBody['item2']="StackOverflow"
print(blockBody.item1)
print(blockBody.item2)

Wynik:

10000
StackOverflow
IRSHAD
źródło
4

Sam język tego nie obsługuje, ale czasem jest to nadal użyteczny wymóg. Oprócz przepisu Bunch możesz także napisać małą metodę, która może uzyskać dostęp do słownika za pomocą kropkowanego ciągu:

def get_var(input_dict, accessor_string):
    """Gets data from a dictionary using a dotted accessor-string"""
    current_data = input_dict
    for chunk in accessor_string.split('.'):
        current_data = current_data.get(chunk, {})
    return current_data

który obsługuje coś takiego:

>> test_dict = {'thing': {'spam': 12, 'foo': {'cheeze': 'bar'}}}
>> output = get_var(test_dict, 'thing.spam.foo.cheeze')
>> print output
'bar'
>>
pbanka
źródło
4

Opierając się na odpowiedzi epool, ta wersja umożliwia dostęp do dowolnego nagrania wewnątrz za pomocą operatora kropki:

foo = {
    "bar" : {
        "baz" : [ {"boo" : "hoo"} , {"baba" : "loo"} ]
    }
}

Na przykład foo.bar.baz[1].babazwraca "loo".

class Map(dict):
    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():
                    if isinstance(v, dict):
                        v = Map(v)
                    if isinstance(v, list):
                        self.__convert(v)
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                if isinstance(v, dict):
                    v = Map(v)
                elif isinstance(v, list):
                    self.__convert(v)
                self[k] = v

    def __convert(self, v):
        for elem in xrange(0, len(v)):
            if isinstance(v[elem], dict):
                v[elem] = Map(v[elem])
            elif isinstance(v[elem], list):
                self.__convert(v[elem])

    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]
dotykać mojego ciała
źródło
1
Python 3: zastąpienie iteritems()ze items()i xrange()zerange()
sasawatc
3
def dict_to_object(dick):
    # http://stackoverflow.com/a/1305663/968442

    class Struct:
        def __init__(self, **entries):
            self.__dict__.update(entries)

    return Struct(**dick)

Jeśli ktoś zdecyduje się na stałe przekonwertować to dictna obiekt, powinien to zrobić. Możesz stworzyć obiekt do rzucania tuż przed uzyskaniem dostępu.

d = dict_to_object(d)
nehem
źródło
def attr (** kwargs): o = lambda: None o .__ dict __. update (** kwargs) return o
throws_exceptions_at_you 31.01.19
2

Skończyło się na próbowaniu ZARÓWNO AttrDict i Bunchbibliotek i odkryłem, że jest to sposób na spowolnienie dla moich zastosowań. Po przyjrzeniu się temu przyjacielowi stwierdziliśmy, że główna metoda pisania tych bibliotek powoduje, że biblioteka agresywnie rekurencyjnie przeszukuje zagnieżdżony obiekt i tworzy kopie obiektu słownika w całym tekście. Mając to na uwadze, wprowadziliśmy dwie kluczowe zmiany. 1) Stworzyliśmy leniwe atrybuty 2) zamiast tworzyć kopie obiektu słownika, tworzymy kopie lekkiego obiektu proxy. To jest ostateczne wdrożenie. Wzrost wydajności korzystania z tego kodu jest niesamowity. Podczas korzystania z AttrDict lub Bunch te dwie biblioteki same pochłonęły odpowiednio 1/2 i 1/3 mojego czasu żądania (co !?). Ten kod skrócił ten czas prawie do zera (gdzieś w zakresie 0,5 ms). Zależy to oczywiście od twoich potrzeb, ale jeśli używasz tej funkcji całkiem sporo w kodzie,

class DictProxy(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return wrap(self.obj[key])

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

    # you probably also want to proxy important list properties along like
    # items(), iteritems() and __len__

class ListProxy(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return wrap(self.obj[key])

    # you probably also want to proxy important list properties along like
    # __iter__ and __len__

def wrap(value):
    if isinstance(value, dict):
        return DictProxy(value)
    if isinstance(value, (tuple, list)):
        return ListProxy(value)
    return value

Zobacz oryginalną implementację tutaj : https://stackoverflow.com/users/704327/michael-merickel .

Inną rzeczą do odnotowania jest to, że ta implementacja jest dość prosta i nie implementuje wszystkich metod, których możesz potrzebować. Musisz zapisać je zgodnie z wymaganiami na obiektach DictProxy lub ListProxy.

JayD3e
źródło
0

Chciałbym wrzucić własne rozwiązanie do ringu:

https://github.com/skorokithakis/jsane

Pozwala ci parsować JSON w coś, do czego masz dostęp with.attribute.lookups.like.this.r(), głównie dlatego, że nie widziałem tej odpowiedzi przed rozpoczęciem pracy nad nią.

Stavros Korokithakis
źródło
Python jest winny kilku irytujących prostych błędów projektowych, podniesienie KeyErrorjest jednym z nich, gdy dostęp do klucza, który nie istnieje, wszystko, co musi zrobić, to powrót Nonepodobny do zachowania JS. Jestem wielkim fanem automatyzacji zarówno do czytania, jak i pisania. Twoja biblioteka jest najbliższa ideału.
nehem
0

Nie bezpośrednia odpowiedź na pytanie OP, ale zainspirowana i być może przydatna dla niektórych. Stworzyłem rozwiązanie obiektowe przy użyciu wewnętrznego __dict__(w żaden sposób zoptymalizowanego kodu)

payload = {
    "name": "John",
    "location": {
        "lat": 53.12312312,
        "long": 43.21345112
    },
    "numbers": [
        {
            "role": "home",
            "number": "070-12345678"
        },
        {
            "role": "office",
            "number": "070-12345679"
        }
    ]
}


class Map(object):
    """
    Dot style access to object members, access raw values
    with an underscore e.g.

    class Foo(Map):
        def foo(self):
            return self.get('foo') + 'bar'

    obj = Foo(**{'foo': 'foo'})

    obj.foo => 'foobar'
    obj._foo => 'foo'

    """

    def __init__(self, *args, **kwargs):
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self.__dict__[k] = v
                    self.__dict__['_' + k] = v

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

    def __getattribute__(self, attr):
        if hasattr(self, 'get_' + attr):
            return object.__getattribute__(self, 'get_' + attr)()
        else:
            return object.__getattribute__(self, attr)

    def get(self, key):
        try:
            return self.__dict__.get('get_' + key)()
        except (AttributeError, TypeError):
            return self.__dict__.get(key)

    def __repr__(self):
        return u"<{name} object>".format(
            name=self.__class__.__name__
        )


class Number(Map):
    def get_role(self):
        return self.get('role')

    def get_number(self):
        return self.get('number')


class Location(Map):
    def get_latitude(self):
        return self.get('lat') + 1

    def get_longitude(self):
        return self.get('long') + 1


class Item(Map):
    def get_name(self):
        return self.get('name') + " Doe"

    def get_location(self):
        return Location(**self.get('location'))

    def get_numbers(self):
        return [Number(**n) for n in self.get('numbers')]


# Tests

obj = Item({'foo': 'bar'}, **payload)

assert type(obj) == Item
assert obj._name == "John"
assert obj.name == "John Doe"
assert type(obj.location) == Location
assert obj.location._lat == 53.12312312
assert obj.location._long == 43.21345112
assert obj.location.latitude == 54.12312312
assert obj.location.longitude == 44.21345112

for n in obj.numbers:
    assert type(n) == Number
    if n.role == 'home':
        assert n.number == "070-12345678"
    if n.role == 'office':
        assert n.number == "070-12345679"
Hedde van der Heide
źródło
0

Jednym prostym sposobem uzyskania dostępu do kropki (ale nie dostępu do tablicy) jest użycie zwykłego obiektu w Pythonie. Lubię to:

class YourObject:
    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

... i użyj go w ten sposób:

>>> obj = YourObject(key="value")
>>> print(obj.key)
"value"

... aby przekonwertować go na dyktando:

>>> print(obj.__dict__)
{"key": "value"}
Emil Stenström
źródło
0

To rozwiązanie jest udoskonaleniem tego, które oferuje epool, aby spełnić wymaganie OP, aby uzyskać dostęp do zagnieżdżonych nagrań w spójny sposób. Rozwiązanie epool nie pozwalało na dostęp do zagnieżdżonych nagrań.

class YAMLobj(dict):
    def __init__(self, args):
        super(YAMLobj, self).__init__(args)
        if isinstance(args, dict):
            for k, v in args.iteritems():
                if not isinstance(v, dict):
                    self[k] = v
                else:
                    self.__setattr__(k, YAMLobj(v))


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

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

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

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

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

Z tej klasy można teraz zrobić coś takiego: A.B.C.D.

deepak
źródło
0

Działa to również w przypadku zagnieżdżonych nagrań i zapewnia, że ​​dołączone później nagrania zachowują się tak samo:

class DotDict(dict):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Recursively turn nested dicts into DotDicts
        for key, value in self.items():
            if type(value) is dict:
                self[key] = DotDict(value)

    def __setitem__(self, key, item):
        if type(item) is dict:
            item = DotDict(item)
        super().__setitem__(key, item)

    __setattr__ = __setitem__
    __getattr__ = dict.__getitem__
Yaniv K.
źródło
0

Odpowiedź @ derek73 jest bardzo zgrabna, ale nie można go marynować ani (głęboko) skopiować i zwraca Noneza brakujące klucze. Poniższy kod to rozwiązuje.

Edycja: Nie widziałem powyższej odpowiedzi, która dotyczy dokładnie tego samego punktu (pozytywnie oceniany). Zostawiam odpowiedź tutaj w celach informacyjnych.

class dotdict(dict):
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)
marnix
źródło
-1

Rozwiązanie delikatne

class DotDict(dict):

    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __getattr__(self, key):

        def typer(candidate):
            if isinstance(candidate, dict):
                return DotDict(candidate)

            if isinstance(candidate, str):  # iterable but no need to iter
                return candidate

            try:  # other iterable are processed as list
                return [typer(item) for item in candidate]
            except TypeError:
                return candidate

            return candidate

        return typer(dict.get(self, key))
Yonks Somarl
źródło