Konwertuje zagnieżdżony dict Pythona na obiekt?

537

Szukam eleganckiego sposobu na uzyskanie danych za pomocą dostępu do atrybutu w nagraniu z niektórymi zagnieżdżonymi dyktami i listami (tj. Składnią obiektu w stylu javascript).

Na przykład:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

Powinny być dostępne w ten sposób:

>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
bar

Myślę, że nie jest to możliwe bez rekurencji, ale jaki byłby fajny sposób na uzyskanie stylu obiektowego dla nagrań?

Marc
źródło
6
Ostatnio próbowałem zrobić coś podobnego, ale powtarzający się klucz słownika („from” - który jest słowem kluczowym Python) uniemożliwił mi to. Ponieważ jak tylko spróbujesz użyć „x.from”, aby uzyskać dostęp do tego atrybutu, otrzymasz błąd składniowy.
Dawie Strauss,
3
to rzeczywiście problem, ale mogę zrezygnować z opcji „od”, aby ułatwić życie w uzyskiwaniu dostępu do dużych konstrukcji dict :) wpisywanie x ['a'] ['d'] [1] ['foo'] jest naprawdę denerwujące, więc xad [1]. Zasady Foo. jeśli potrzebujesz z, możesz uzyskać do niego dostęp za pomocą getattr (x, 'from') lub zamiast tego użyć _from jako atrybutu.
Marc
5
from_zamiast _fromzgodnie z PEP 8 .
Kos
1
Możesz użyć getattr(x, 'from')zamiast zmiany nazwy atrybutu.
George V. Reilly,
3
Większość z tych „rozwiązań” wydaje się nie działać (nawet te przyjęte, nie zezwalają na zagnieżdżanie d1.b.c), myślę, że jasne jest, że powinieneś używać czegoś z biblioteki, np. Nametuple ze zbiorów , jak sugeruje ta odpowiedź … .
Andy Hayden

Odpowiedzi:

659

Aktualizacja: w Pythonie 2.6 i nowszych rozważ, czy namedtuplestruktura danych odpowiada Twoim potrzebom:

>>> from collections import namedtuple
>>> MyStruct = namedtuple('MyStruct', 'a b d')
>>> s = MyStruct(a=1, b={'c': 2}, d=['hi'])
>>> s
MyStruct(a=1, b={'c': 2}, d=['hi'])
>>> s.a
1
>>> s.b
{'c': 2}
>>> s.c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyStruct' object has no attribute 'c'
>>> s.d
['hi']

Alternatywą (oryginalna treść odpowiedzi) jest:

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

Następnie możesz użyć:

>>> args = {'a': 1, 'b': 2}
>>> s = Struct(**args)
>>> s
<__main__.Struct instance at 0x01D6A738>
>>> s.a
1
>>> s.b
2
Eli Bendersky
źródło
19
To samo tutaj - jest to szczególnie przydatne do rekonstrukcji obiektów Python z baz danych zorientowanych na dokumenty, takich jak MongoDB.
mikemaccana
13
Aby uzyskać ładniejsze drukowanie, dodaj: def repr __ (self): return '<% s>'% str ('\ n' .join ('% s:% s'% (k, repr (v)) dla (k, v ) in self .__ dict .iteritems ()))
six8
15
Czy będzie działać z zagnieżdżonymi słownikami? i dykty zawierające przedmioty i / lub listę itp. Czy są jakieś połowy?
Sam Stoelinga,
5
@Sam S: nie utworzy zagnieżdżonych Structs ze zagnieżdżonych słowników, ale ogólnie typ wartości może być dowolny. Klucz ogranicza się do dopasowania do miejsca na przedmioty
Eli Bendersky,
51
-1, ponieważ a) nie działa dla zagnieżdżonych dicts, której pytanie wyraźnie poprosił o, oraz b) te same funkcje obecnie istnieje w standardowych bibliotekach argparse.Namespace(który ma również określone __eq__, __ne__, __contains__).
wim
110
class obj(object):
    def __init__(self, d):
        for a, b in d.items():
            if isinstance(b, (list, tuple)):
               setattr(self, a, [obj(x) if isinstance(x, dict) else x for x in b])
            else:
               setattr(self, a, obj(b) if isinstance(b, dict) else b)

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = obj(d)
>>> x.b.c
2
>>> x.d[1].foo
'bar'
Nadia Alramli
źródło
7
Dobrze! Zastąpiłbym .items () przez .iteritems (), aby uzyskać mniejszą pamięć.
Eric O Lebigot,
7
Jeśli nie jest to wymóg OP, nie stanowi to problemu - należy jednak pamiętać, że nie spowoduje to rekurencyjnego przetwarzania obiektów na listach w ramach list.
Anon,
3
Zdaję sobie sprawę, że to stara odpowiedź, ale w dzisiejszych czasach lepiej byłoby użyć abstrakcyjnej klasy bazowej zamiast tej brzydkiej liniiif isinstance(b, (list, tuple)):
wim 30'13
4
Wskazówka: Niech objdziedziczy klasa argparse.Namespacepo dodatkowe funkcje, takie jak czytelna reprezentacja ciągu.
Serrano
2
W zależności od przypadku użycia, zwykle chcemy sprawdzić, czy to nie rodzaj ciąg jednak, że jest to rodzaj Sequence
wim
107

Zaskakujące, że nikt nie wspomniał o Bunchu . Ta biblioteka służy wyłącznie do zapewnienia dostępu do obiektów typu dict w stylu atrybutów i robi dokładnie to, czego chce OP. Demonstracja:

>>> from bunch import bunchify
>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = bunchify(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

Biblioteka Python 3 jest dostępna na stronie https://github.com/Infinidat/munch - Kredyt trafia do codyzu

ciągłość
źródło
16
I istnieje gałąź Buncha zgodna z Pythonem 3 (wydaje się być 2.6-3.4) o nazwie Munch: github.com/Infinidat/munch
codyzu
Bunch jest najlepszym rozwiązaniem z nich wszystkich, ponieważ dobrze obsługuje wiele typów serializacji i jest utrzymywany. Wielkie dzięki. Chciałbym, aby wersja Python3 została nazwana tak samo, ponieważ dlaczego nie.
Jonathan
4
Więc tylko ostrzeżenie, Bunch i Attrdict również w tej sprawie są bardzo powolne. Zużyli odpowiednio około 1/3 i 1/2 mojego czasu żądania, gdy zobaczyli częste użycie w naszej aplikacji. Zdecydowanie nie jest to coś, co można zignorować. Porozmawiaj o tym więcej stackoverflow.com/a/31569634/364604 .
JayD3e
Chociaż pozwala to na obiektowy dostęp do nagrań, robi to poprzez getattr . Utrudnia to introspekcję w inteligentnych REPL, takich jak IPython.
GDorn,
Ciekawe, jak to się ma do JSONpath github.com/kennknowles/python-jsonpath-rw
MarkHu 20.04.2016
64
x = type('new_dict', (object,), d)

następnie dodaj do tego rekursję i gotowe.

edytuj to jak to zaimplementuję:

>>> d
{'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> def obj_dic(d):
    top = type('new', (object,), d)
    seqs = tuple, list, set, frozenset
    for i, j in d.items():
        if isinstance(j, dict):
            setattr(top, i, obj_dic(j))
        elif isinstance(j, seqs):
            setattr(top, i, 
                type(j)(obj_dic(sj) if isinstance(sj, dict) else sj for sj in j))
        else:
            setattr(top, i, j)
    return top

>>> x = obj_dic(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'
SilentGhost
źródło
1
Dlaczego tworzysz obiekty typu, a nie tworzysz ich instancji? Czy nie byłoby to bardziej logiczne? Mam na myśli, dlaczego nie zrobić top_instance = top()i zwrócić to tam, gdzie wrócisz top?
naleśnik
6
Nicea za „liścia” dane, ale przykłady wygodnie opuścić „gałązki” Like xa x.bktóre zwracają brzydki<class '__main__.new'>
Markhu
46

Nazywa się pomocnik kolekcji namedtuple, który może to dla Ciebie zrobić:

from collections import namedtuple

d_named = namedtuple('Struct', d.keys())(*d.values())

In [7]: d_named
Out[7]: Struct(a=1, b={'c': 2}, d=['hi', {'foo': 'bar'}])

In [8]: d_named.a
Out[8]: 1
umbrae
źródło
28
To nie odpowiada na pytanie o rekurencję zagnieżdżonych nagrań.
kwalifikowalny
4
nie możesz modyfikować nazwanych świątyń.
Max
Czy możemy zagwarantować, że kolejność listy zwracanej przez keys () i wartości () jest zgodna w kolejności? Mam na myśli, że jeśli to nakaz, tak. Ale standardowy dyktand? Wiem, że dyktanda CPython 3.6+ i PyPy są „kompaktowe”, ale cytują dokumentację: „Aspekt utrzymywania porządku w tej nowej implementacji jest uważany za szczegół implementacji i nie można na nim polegać”
Havok
38
class Struct(object):
    """Comment removed"""
    def __init__(self, data):
        for name, value in data.iteritems():
            setattr(self, name, self._wrap(value))

    def _wrap(self, value):
        if isinstance(value, (tuple, list, set, frozenset)): 
            return type(value)([self._wrap(v) for v in value])
        else:
            return Struct(value) if isinstance(value, dict) else value

Może być stosowany z dowolną strukturą sekwencji / nagrania / wartości o dowolnej głębokości.

XEye
źródło
4
To powinna być odpowiedź. Działa dobrze do zagnieżdżania. Możesz użyć tego jako hook_obiektu również dla json.load ().
010110110101
2
Podobnie do funkcjonalnej odpowiedzi SilentGhost z 2009 roku - dane węzła liścia są dostępne, ale rodzic / gałązki są wyświetlane jako odwołania do obiektu. Do ładnego wydruku,def __repr__(self): return '{%s}' % str(', '.join("'%s': %s" % (k, repr(v)) for (k, v) in self.__dict__.iteritems()))
MarkHu
5
Użytkownicy .items()języka Python 3.x: po prostu zamiast .iteritems()w wierszu 4. (Nazwa została zmieniona, ale zasadniczo jest taka sama )
neo post modern
Działa idealnie dla zagnieżdżonych obiektów, dzięki! Jako komentarz początkujący jak ja, w python3 iteritems () musi zostać zmieniony na items ()
Óscar Andreu
31

Biorąc to, co uważam za najlepsze aspekty poprzednich przykładów, oto co wymyśliłem:

class Struct:
  '''The recursive class for building and representing objects with.'''
  def __init__(self, obj):
    for k, v in obj.iteritems():
      if isinstance(v, dict):
        setattr(self, k, Struct(v))
      else:
        setattr(self, k, v)
  def __getitem__(self, val):
    return self.__dict__[val]
  def __repr__(self):
    return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for
      (k, v) in self.__dict__.iteritems()))
andyvanee
źródło
Zauważ, że konstruktor można skrócić do: def __init__(self, dct): for k, v in dct.iteritems(): setattr(self, k, isinstance(v, dict) and self.__class__(v) or v) co również usuwa jawne wezwanie doStruct
George V. Reilly
2
Wolałbym nie głosować za moją własną odpowiedzią, ale spoglądając wstecz na to zauważyłem, że nie przechodzi ona w typy sekwencji. xd [1] .foo kończy się niepowodzeniem w tym przypadku.
andyvanee
2
isinstance(v, dict)check będzie lepiej, jak isinstance(v, collections.Mapping)więc może obsłużyć przyszłość dict-jak rzeczy
kuchenki
29

Jeśli pochodzi Twój dykt json.loads(), możesz go przekształcić w obiekt (zamiast dykta) w jednym wierszu:

import json
from collections import namedtuple

json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))

Zobacz także Jak przekonwertować dane JSON na obiekt Python .

DS.
źródło
Wydaje się być znacznie wolniejszy niż zwykły, .loadsjeśli masz ogromny obiekt JSON
michaelsnowden
16

Jeśli chcesz uzyskać dostęp do kluczy dyktafonu jako obiektu (lub dykta dla trudnych klawiszy), zrób to rekurencyjnie, a także możesz zaktualizować oryginalny dykta, możesz:

class Dictate(object):
    """Object view of a dict, updating the passed in dict when values are set
    or deleted. "Dictate" the contents of a dict...: """

    def __init__(self, d):
        # since __setattr__ is overridden, self.__dict = d doesn't work
        object.__setattr__(self, '_Dictate__dict', d)

    # Dictionary-like access / updates
    def __getitem__(self, name):
        value = self.__dict[name]
        if isinstance(value, dict):  # recursively view sub-dicts as objects
            value = Dictate(value)
        return value

    def __setitem__(self, name, value):
        self.__dict[name] = value
    def __delitem__(self, name):
        del self.__dict[name]

    # Object-like access / updates
    def __getattr__(self, name):
        return self[name]

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

    def __repr__(self):
        return "%s(%r)" % (type(self).__name__, self.__dict)
    def __str__(self):
        return str(self.__dict)

Przykładowe użycie:

d = {'a': 'b', 1: 2}
dd = Dictate(d)
assert dd.a == 'b'  # Access like an object
assert dd[1] == 2  # Access like a dict
# Updates affect d
dd.c = 'd'
assert d['c'] == 'd'
del dd.a
del dd[1]
# Inner dicts are mapped
dd.e = {}
dd.e.f = 'g'
assert dd['e'].f == 'g'
assert d == {'c': 'd', 'e': {'f': 'g'}}
użytkownik1783597
źródło
13
>>> def dict2obj(d):
        if isinstance(d, list):
            d = [dict2obj(x) for x in d]
        if not isinstance(d, dict):
            return d
        class C(object):
            pass
        o = C()
        for k in d:
            o.__dict__[k] = dict2obj(d[k])
        return o


>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'
Zaraz
źródło
12

Skończyło się na próbowaniu ZARÓWNO AttrDict i Bunchbibliotek i stwierdził, że są one zbyt wolne 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 atrybuty leniwie załadowane 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). To oczywiście zależy od twoich potrzeb,

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
9

x.__dict__.update(d) powinno być w porządku.

Alex Rodrigues
źródło
dzięki za odpowiedź, ale co to jest x? dykt czy standardowy obiekt? czy mógłbyś mi podpowiedzieć?
Marc
x jest twoim przedmiotem. Każdy przedmiot ma dyktando . Aktualizując słownik obiektu, aktualizujesz w nim kluczowe zmienne.
Alex Rodrigues,
Odważne słowa to _ _ dict _ _
Alex Rodrigues,
15
Nie będzie to obsługiwać zagnieżdżonych słowników.
FogleBird,
Nie każdy obiekt ma __dict__: spróbuj, object().__dict__a dostanieszAttributeError: 'object' object has no attribute '__dict__'
Will Manley
8

Możesz wykorzystać jsonmoduł biblioteki standardowej za pomocą niestandardowego zaczepu obiektu :

import json

class obj(object):
    def __init__(self, dict_):
        self.__dict__.update(dict_)

def dict2obj(d):
    return json.loads(json.dumps(d), object_hook=obj)

Przykładowe użycie:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> o = dict2obj(d)
>>> o.a
1
>>> o.b.c
2
>>> o.d[0]
u'hi'
>>> o.d[1].foo
u'bar'

I to jest nie ściśle tylko do odczytu , jak to jest z namedtuple, czyli można zmieniać wartości - nie struktura:

>>> o.b.c = 3
>>> o.b.c
3
Vanni Totaro
źródło
1
Jak dotąd najlepsza odpowiedź
Connor
Podoba mi się pomysł użycia mechanizmu ładowania json dla elementów zagnieżdżonych. Ale mamy już dyktand i nie podoba mi się fakt, że trzeba z niego utworzyć ciąg, aby zamapować go na obiekt. Wolałbym mieć rozwiązanie, które tworzy obiekty bezpośrednio z nagrania.
Sandro
1
Najpierw trzeba przekonwertować na ciąg, ale dość ogólny, ponieważ można go rozszerzyć za pomocą json.JSONEncoderi object_hook.
Sandro
6

To powinno zacząć:

class dict2obj(object):
    def __init__(self, d):
        self.__dict__['d'] = d

    def __getattr__(self, key):
        value = self.__dict__['d'][key]
        if type(value) == type({}):
            return dict2obj(value)

        return value

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)
print x.a
print x.b.c
print x.d[1].foo

To jeszcze nie działa w przypadku list. Będziesz musiał zawinąć listy w UserList i przeładować, __getitem__aby zawinąć dyktanda.

Aaron Digulla
źródło
2
Aby działało na listach, użyj if isinstance(d, list)klauzuli z Anonodpowiedzi.
Vinay Sajip,
6

Wiem, że jest tu już wiele odpowiedzi i spóźniam się na imprezę, ale ta metoda rekursywnie i „na miejscu” przekształci słownik w obiektową strukturę ... Działa w wersji 3.xx

def dictToObject(d):
    for k,v in d.items():
        if isinstance(v, dict):
            d[k] = dictToObject(v)
    return namedtuple('object', d.keys())(*d.values())

# Dictionary created from JSON file
d = {
    'primaryKey': 'id', 
    'metadata': 
        {
            'rows': 0, 
            'lastID': 0
        }, 
    'columns': 
        {
            'col2': {
                'dataType': 'string', 
                'name': 'addressLine1'
            }, 
            'col1': {
                'datatype': 'string', 
                'name': 'postcode'
            }, 
            'col3': {
                'dataType': 'string', 
                'name': 'addressLine2'
            }, 
            'col0': {
                'datatype': 'integer', 
                'name': 'id'
            }, 
            'col4': {
                'dataType': 'string', 
                'name': 'contactNumber'
            }
        }, 
        'secondaryKeys': {}
}

d1 = dictToObject(d)
d1.columns.col1 # == object(datatype='string', name='postcode')
d1.metadata.rows # == 0
Aidan Haddon-Wright
źródło
Co masz na myśli mówiąc „obiektowa struktura”?
MoralCode
1
Obiektowy ze względu na zasadę pisania kaczego. Mam na myśli to, że możesz uzyskać dostęp do jego właściwości tak, jak gdybyś był instancją klasy. ale NIE jest to obiekt w tym sensie, to nazwana krotka.
Aidan Haddon-Wright
5
from mock import Mock
d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
my_data = Mock(**d)

# We got
# my_data.a == 1
Naprzód
źródło
4

Pozwól, że wyjaśnię rozwiązanie, z którego prawie kiedyś korzystałem. Ale po pierwsze, powodem, dla którego tego nie zrobiłem, jest fakt, że następujący kod:

d = {'from': 1}
x = dict2obj(d)

print x.from

daje ten błąd:

  File "test.py", line 20
    print x.from == 1
                ^
SyntaxError: invalid syntax

Ponieważ „from” jest słowem kluczowym Python, istnieją pewne klucze słownika, na które nie można zezwolić.


Teraz moje rozwiązanie umożliwia dostęp do elementów słownika poprzez bezpośrednie używanie ich nazw. Ale pozwala także na użycie „semantyki słownika”. Oto kod z przykładowym użyciem:

class dict2obj(dict):
    def __init__(self, dict_):
        super(dict2obj, self).__init__(dict_)
        for key in self:
            item = self[key]
            if isinstance(item, list):
                for idx, it in enumerate(item):
                    if isinstance(it, dict):
                        item[idx] = dict2obj(it)
            elif isinstance(item, dict):
                self[key] = dict2obj(item)

    def __getattr__(self, key):
        return self[key]

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)

assert x.a == x['a'] == 1
assert x.b.c == x['b']['c'] == 2
assert x.d[1].foo == x['d'][1]['foo'] == "bar"
Dawie Strauss
źródło
1
klasa Struct: def init __ (self, ** wpisy): self .__ dict .update (wpisy)
Kenneth Reitz
4

Stare pytania i odpowiedzi, ale mam coś więcej do powiedzenia. Wydaje się, że nikt nie mówi o rekursywnym dykcie. To jest mój kod:

#!/usr/bin/env python

class Object( dict ):
    def __init__( self, data = None ):
        super( Object, self ).__init__()
        if data:
            self.__update( data, {} )

    def __update( self, data, did ):
        dataid = id(data)
        did[ dataid ] = self

        for k in data:
            dkid = id(data[k])
            if did.has_key(dkid):
                self[k] = did[dkid]
            elif isinstance( data[k], Object ):
                self[k] = data[k]
            elif isinstance( data[k], dict ):
                obj = Object()
                obj.__update( data[k], did )
                self[k] = obj
                obj = None
            else:
                self[k] = data[k]

    def __getattr__( self, key ):
        return self.get( key, None )

    def __setattr__( self, key, value ):
        if isinstance(value,dict):
            self[key] = Object( value )
        else:
            self[key] = value

    def update( self, *args ):
        for obj in args:
            for k in obj:
                if isinstance(obj[k],dict):
                    self[k] = Object( obj[k] )
                else:
                    self[k] = obj[k]
        return self

    def merge( self, *args ):
        for obj in args:
            for k in obj:
                if self.has_key(k):
                    if isinstance(self[k],list) and isinstance(obj[k],list):
                        self[k] += obj[k]
                    elif isinstance(self[k],list):
                        self[k].append( obj[k] )
                    elif isinstance(obj[k],list):
                        self[k] = [self[k]] + obj[k]
                    elif isinstance(self[k],Object) and isinstance(obj[k],Object):
                        self[k].merge( obj[k] )
                    elif isinstance(self[k],Object) and isinstance(obj[k],dict):
                        self[k].merge( obj[k] )
                    else:
                        self[k] = [ self[k], obj[k] ]
                else:
                    if isinstance(obj[k],dict):
                        self[k] = Object( obj[k] )
                    else:
                        self[k] = obj[k]
        return self

def test01():
    class UObject( Object ):
        pass
    obj = Object({1:2})
    d = {}
    d.update({
        "a": 1,
        "b": {
            "c": 2,
            "d": [ 3, 4, 5 ],
            "e": [ [6,7], (8,9) ],
            "self": d,
        },
        1: 10,
        "1": 11,
        "obj": obj,
    })
    x = UObject(d)


    assert x.a == x["a"] == 1
    assert x.b.c == x["b"]["c"] == 2
    assert x.b.d[0] == 3
    assert x.b.d[1] == 4
    assert x.b.e[0][0] == 6
    assert x.b.e[1][0] == 8
    assert x[1] == 10
    assert x["1"] == 11
    assert x[1] != x["1"]
    assert id(x) == id(x.b.self.b.self) == id(x.b.self)
    assert x.b.self.a == x.b.self.b.self.a == 1

    x.x = 12
    assert x.x == x["x"] == 12
    x.y = {"a":13,"b":[14,15]}
    assert x.y.a == 13
    assert x.y.b[0] == 14

def test02():
    x = Object({
        "a": {
            "b": 1,
            "c": [ 2, 3 ]
        },
        1: 6,
        2: [ 8, 9 ],
        3: 11,
    })
    y = Object({
        "a": {
            "b": 4,
            "c": [ 5 ]
        },
        1: 7,
        2: 10,
        3: [ 12 , 13 ],
    })
    z = {
        3: 14,
        2: 15,
        "a": {
            "b": 16,
            "c": 17,
        }
    }
    x.merge( y, z )
    assert 2 in x.a.c
    assert 3 in x.a.c
    assert 5 in x.a.c
    assert 1 in x.a.b
    assert 4 in x.a.b
    assert 8 in x[2]
    assert 9 in x[2]
    assert 10 in x[2]
    assert 11 in x[3]
    assert 12 in x[3]
    assert 13 in x[3]
    assert 14 in x[3]
    assert 15 in x[2]
    assert 16 in x.a.b
    assert 17 in x.a.c

if __name__ == '__main__':
    test01()
    test02()
truease.com
źródło
4

Chciałem przesłać moją wersję tego małego paradygmatu.

class Struct(dict):
  def __init__(self,data):
    for key, value in data.items():
      if isinstance(value, dict):
        setattr(self, key, Struct(value))
      else:   
        setattr(self, key, type(value).__init__(value))

      dict.__init__(self,data)

Zachowuje atrybuty typu importowanego do klasy. Moją jedyną troską byłoby zastąpienie metod parsowania ze słownika. Ale poza tym wydaje się solidny!

Erik
źródło
Chociaż to fajny pomysł, nie wydaje się to działać na przykład OP, w rzeczywistości wydaje się modyfikować przekazany słownik! W rzeczywistości df.anawet nie działa.
Andy Hayden
4

To też działa dobrze

class DObj(object):
    pass

dobj = Dobj()
dobj.__dict__ = {'a': 'aaa', 'b': 'bbb'}

print dobj.a
>>> aaa
print dobj.b
>>> bbb
naren
źródło
3

Oto inny sposób implementacji oryginalnej sugestii SilentGhost:

def dict2obj(d):
  if isinstance(d, dict):
    n = {}
    for item in d:
      if isinstance(d[item], dict):
        n[item] = dict2obj(d[item])
      elif isinstance(d[item], (list, tuple)):
        n[item] = [dict2obj(elem) for elem in d[item]]
      else:
        n[item] = d[item]
    return type('obj_from_dict', (object,), n)
  else:
    return d
obrabować
źródło
3

Natknąłem się na przypadek, w którym musiałem rekurencyjnie przekonwertować listę dykt na listę obiektów, więc na podstawie fragmentu Roberta tutaj, co zadziałało dla mnie:

def dict2obj(d):
    if isinstance(d, dict):
        n = {}
        for item in d:
            if isinstance(d[item], dict):
                n[item] = dict2obj(d[item])
            elif isinstance(d[item], (list, tuple)):
                n[item] = [dict2obj(elem) for elem in d[item]]
            else:
                n[item] = d[item]
        return type('obj_from_dict', (object,), n)
    elif isinstance(d, (list, tuple,)):
        l = []
        for item in d:
            l.append(dict2obj(item))
        return l
    else:
        return d

Zauważ, że każda krotka zostanie z oczywistych powodów przekonwertowana na ekwiwalent listy.

Mam nadzieję, że to pomaga komuś tak samo jak wszystkie twoje odpowiedzi, chłopaki.

NiKo
źródło
3

Co powiesz na przypisanie dictdo __dict__pustego obiektu?

class Object:
    """If your dict is "flat", this is a simple way to create an object from a dict

    >>> obj = Object()
    >>> obj.__dict__ = d
    >>> d.a
    1
    """
    pass

Oczywiście nie powiedzie się to na przykładzie zagnieżdżonego słownika, chyba że rekursywnie przeprowadzisz go:

# For a nested dict, you need to recursively update __dict__
def dict2obj(d):
    """Convert a dict to an object

    >>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
    >>> obj = dict2obj(d)
    >>> obj.b.c
    2
    >>> obj.d
    ["hi", {'foo': "bar"}]
    """
    try:
        d = dict(d)
    except (TypeError, ValueError):
        return d
    obj = Object()
    for k, v in d.iteritems():
        obj.__dict__[k] = dict2obj(v)
    return obj

Twój przykładowy element listy prawdopodobnie miał być Mappinglistą (klucz, wartość) w następujący sposób:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': [("hi", {'foo': "bar"})]}
>>> obj = dict2obj(d)
>>> obj.d.hi.foo
"bar"
płyty grzewcze
źródło
2

Oto kolejna implementacja:

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

def dict_to_obj(d):
    if isinstance(d, (list, tuple)): return map(dict_to_obj, d)
    elif not isinstance(d, dict): return d
    return DictObj(dict((k, dict_to_obj(v)) for (k,v) in d.iteritems()))

[Edytuj] Brakowało trochę na temat obsługi dykt na listach, a nie tylko innych dykt. Dodano poprawkę.

Brian
źródło
Zauważ, że ustawienie dict w słowniku źródłowym oznacza, że ​​wszelkie zmiany atrybutów w wynikowym obiekcie wpłyną również na słownik, który utworzył obiekt i odwrotnie. Może to prowadzić do nieoczekiwanych rezultatów, jeśli słownik zostanie użyty do czegoś innego niż tworzenie obiektu.
Mark Roddy
@Mark: W rzeczywistości za każdym razem do DictObj przekazywany jest nowy słownik, a nie tylko ten sam obiekt dict, więc tak się nie stanie. Jest to konieczne, ponieważ muszę również przetłumaczyć wartości w słowniku, więc przejście przez oryginalny obiekt dict nie byłoby możliwe bez jego samodzielnej mutacji.
Brian
Jest na to sporo odpowiedzi, zaakceptowana odpowiedź nie wyglądała tak, jakby była rekurencyjna lub obsługiwała listy. Przeczytałem je wszystkie i ten wyglądał na najprostszy od pierwszego wejrzenia, przetestowałem i zadziałało. Świetna odpowiedź.
AlwaysTraining 24.04.19
2
class Struct(dict):
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

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

    def copy(self):
        return Struct(dict.copy(self))

Stosowanie:

points = Struct(x=1, y=2)
# Changing
points['x'] = 2
points.y = 1
# Accessing
points['x'], points.x, points.get('x') # 2 2 2
points['y'], points.y, points.get('y') # 1 1 1
# Accessing inexistent keys/attrs 
points['z'] # KeyError: z
points.z # AttributeError: z
# Copying
points_copy = points.copy()
points.x = 2
points_copy.x # 1
Paulo Freitas
źródło
2

Co powiesz na to:

from functools import partial
d2o=partial(type, "d2o", ())

Można to następnie wykorzystać w następujący sposób:

>>> o=d2o({"a" : 5, "b" : 3})
>>> print o.a
5
>>> print o.b
3
na nie
źródło
2

Myślę, że dykt składa się z liczby, ciągu i dykt wystarcza przez większość czasu. Dlatego ignoruję sytuację, w której krotki, listy i inne typy nie pojawiają się w ostatecznym wymiarze dykta.

Biorąc pod uwagę dziedziczenie w połączeniu z rekurencją, wygodnie rozwiązuje problem z drukowaniem, a także zapewnia dwa sposoby wyszukiwania danych, jeden sposób edycji danych.

Zobacz poniższy przykład, który opisuje niektóre informacje o uczniach:

group=["class1","class2","class3","class4",]
rank=["rank1","rank2","rank3","rank4","rank5",]
data=["name","sex","height","weight","score"]

#build a dict based on the lists above
student_dic=dict([(g,dict([(r,dict([(d,'') for d in data])) for r in rank ]))for g in group])

#this is the solution
class dic2class(dict):
    def __init__(self, dic):
        for key,val in dic.items():
            self.__dict__[key]=self[key]=dic2class(val) if isinstance(val,dict) else val


student_class=dic2class(student_dic)

#one way to edit:
student_class.class1.rank1['sex']='male'
student_class.class1.rank1['name']='Nan Xiang'

#two ways to query:
print student_class.class1.rank1
print student_class.class1['rank1']
print '-'*50
for rank in student_class.class1:
    print getattr(student_class.class1,rank)

Wyniki:

{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
--------------------------------------------------
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
tcpiper
źródło
2

Zazwyczaj chcesz odzwierciedlać hierarchię dyktowania w swoim obiekcie, ale nie wyświetlać list ani krotek, które zwykle znajdują się na najniższym poziomie. Oto jak to zrobiłem:

class defDictToObject(object):

    def __init__(self, myDict):
        for key, value in myDict.items():
            if type(value) == dict:
                setattr(self, key, defDictToObject(value))
            else:
                setattr(self, key, value)

Więc robimy:

myDict = { 'a': 1,
           'b': { 
              'b1': {'x': 1,
                    'y': 2} },
           'c': ['hi', 'bar'] 
         }

i dostać:

x.b.b1.x 1

x.c [„cześć”, „bar”]

VengaVenga
źródło
1

Mój słownik ma ten format:

addr_bk = {
    'person': [
        {'name': 'Andrew', 'id': 123, 'email': '[email protected]',
         'phone': [{'type': 2, 'number': '633311122'},
                   {'type': 0, 'number': '97788665'}]
        },
        {'name': 'Tom', 'id': 456,
         'phone': [{'type': 0, 'number': '91122334'}]}, 
        {'name': 'Jack', 'id': 7788, 'email': '[email protected]'}
    ]
}

Jak widać, zagnieżdżiłem słowniki i listę dykt . Wynika to z faktu, że addr_bk został zdekodowany z danych bufora protokołu, które przekonwertowano na dict Pythona za pomocą lwpb.codec. Istnieją pola opcjonalne (np. E-mail => gdzie klucz może być niedostępny) i pole powtarzane (np. Telefon => przekonwertowane na listę nagrań).

Wypróbowałem wszystkie wyżej zaproponowane rozwiązania. Niektóre nie radzą sobie dobrze z zagnieżdżonymi słownikami. Inni nie mogą łatwo wydrukować szczegółów obiektu.

Tylko rozwiązanie, dict2obj (dict) autorstwa Dawie Strauss, działa najlepiej.

Ulepszyłem go trochę, gdy nie można znaleźć klucza:

# Work the best, with nested dictionaries & lists! :)
# Able to print out all items.
class dict2obj_new(dict):
    def __init__(self, dict_):
        super(dict2obj_new, self).__init__(dict_)
        for key in self:
            item = self[key]
            if isinstance(item, list):
                for idx, it in enumerate(item):
                    if isinstance(it, dict):
                        item[idx] = dict2obj_new(it)
            elif isinstance(item, dict):
                self[key] = dict2obj_new(item)

    def __getattr__(self, key):
        # Enhanced to handle key not found.
        if self.has_key(key):
            return self[key]
        else:
            return None

Następnie przetestowałem to z:

# Testing...
ab = dict2obj_new(addr_bk)

for person in ab.person:
  print "Person ID:", person.id
  print "  Name:", person.name
  # Check if optional field is available before printing.
  if person.email:
    print "  E-mail address:", person.email

  # Check if optional field is available before printing.
  if person.phone:
    for phone_number in person.phone:
      if phone_number.type == codec.enums.PhoneType.MOBILE:
        print "  Mobile phone #:",
      elif phone_number.type == codec.enums.PhoneType.HOME:
        print "  Home phone #:",
      else:
        print "  Work phone #:",
      print phone_number.number
Whospal
źródło
1

Opierając moją odpowiedź na „ python: Jak dynamicznie dodawać właściwość do klasy? ”:

class data(object):
    def __init__(self,*args,**argd):
        self.__dict__.update(dict(*args,**argd))

def makedata(d):
    d2 = {}
    for n in d:
        d2[n] = trydata(d[n])
    return data(d2)

def trydata(o):
    if isinstance(o,dict):
        return makedata(o)
    elif isinstance(o,list):
        return [trydata(i) for i in o]
    else:
        return o

Wywołujesz makedatasłownik, który chcesz przekonwertować, a może w trydatazależności od tego, czego oczekujesz jako dane wejściowe, i wyrzuca obiekt danych.

Uwagi:

  • Możesz dodać elifs, trydatajeśli potrzebujesz więcej funkcji.
  • Oczywiście to nie zadziała, jeśli chcesz x.a = {}lub podobnie.
  • Jeśli chcesz wersji tylko do odczytu, użyj danych klasy z oryginalnej odpowiedzi .
David X
źródło