Convertir el dictado de Python nested a objeto?

Estoy buscando una forma elegante de obtener datos mediante el acceso de atributos en un dictado con algunos dictados y listas anidadas (es decir, syntax de objetos de estilo javascript).

Por ejemplo:

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

Debe ser accesible de esta manera:

 >>> x = dict2obj(d) >>> xa 1 >>> xbc 2 >>> xd[1].foo bar 

Creo que esto no es posible sin recursión, pero ¿cuál sería una buena manera de obtener un estilo de objeto para dictos?

Actualización: en Python 2.6 y en adelante, considere si la estructura de datos de namedtuple se adapta a sus necesidades:

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

La alternativa (contenido de la respuesta original) es:

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

Entonces, puedes usar:

 >>> args = {'a': 1, 'b': 2} >>> s = Struct(**args) >>> s <__main__.Struct instance at 0x01D6A738> >>> sa 1 >>> sb 2 
 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) >>> xbc 2 >>> xd[1].foo 'bar' 

Sorprendentemente nadie ha mencionado a Bunch . Esta biblioteca está diseñada exclusivamente para proporcionar acceso de estilo de atributo a objetos dict y hace exactamente lo que quiere el OP. Una demostración:

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

Una biblioteca de Python 3 está disponible en https://github.com/Infinidat/munch – El crédito va a codyzu

 x = type('new_dict', (object,), d) 

A continuación, agregue recursión a esto y ya está.

Editar así es como lo implementaría:

 >>> 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) >>> xa 1 >>> xbc 2 >>> xd[1].foo 'bar' 

Hay un ayudante de colección llamado namedtuple , que puede hacer esto por usted:

 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 

Tomando lo que creo que son los mejores aspectos de los ejemplos anteriores, esto es lo que se me ocurrió:

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

Se puede utilizar con cualquier estructura de secuencia / dict / valor de cualquier profundidad.

Si su json.loads() proviene de json.loads() , puede convertirlo en un objeto (en lugar de un dictado) en una línea:

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

Vea también Cómo convertir datos JSON en un objeto Python .

Si desea acceder a las teclas dict como un objeto (o como un dict para teclas difíciles), hágalo de forma recursiva, y también podrá actualizar el dict original, podría hacer lo siguiente:

 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) 

Ejemplo de uso:

 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.ef = 'g' assert dd['e'].f == 'g' assert d == {'c': 'd', 'e': {'f': 'g'}} 
 >>> 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) >>> xa 1 >>> xbc 2 >>> xd[1].foo 'bar' 

Terminé probando AMBAS bibliotecas AttrDict y Bunch y encontré que eran demasiado lentas para mis usos. Después de que un amigo y yo lo investigamos, encontramos que el método principal para escribir estas bibliotecas resulta en que la biblioteca se repita de forma agresiva a través de un objeto nested y haga copias del objeto del diccionario en todo momento. Con esto en mente, hicimos dos cambios clave. 1) Hicimos los atributos con carga lenta 2) En lugar de crear copias de un objeto de diccionario, creamos copias de un objeto de proxy ligero. Esta es la implementación final. El aumento de rendimiento de usar este código es increíble. Cuando usé AttrDict o Bunch, estas dos bibliotecas solo consumieron 1/2 y 1/3 respectivamente de mi tiempo de solicitud (¿¡qué !?). Este código redujo ese tiempo a casi nada (en algún lugar en el rango de 0.5 ms). Por supuesto, esto depende de sus necesidades, pero si está utilizando esta funcionalidad en su código, definitivamente use algo tan simple como este.

 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 

Vea la implementación original aquí en https://stackoverflow.com/users/704327/michael-merickel .

La otra cosa a tener en cuenta es que esta implementación es bastante simple y no implementa todos los métodos que pueda necesitar. Deberá escribirlos según sea necesario en los objetos DictProxy o ListProxy.

x.__dict__.update(d) debería funcionar bien.

Esto debería hacer que comiences:

 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 xa print xbc print xd[1].foo 

Todavía no funciona para las listas. Tendrá que envolver las listas en una Lista de usuarios y sobrecargar __getitem__ para envolver los dictados.

Déjame explicarte una solución que casi usé hace algún tiempo. Pero primero, la razón por la que no lo hice se ilustra por el hecho de que el siguiente código:

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

da este error:

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

Debido a que “de” es una palabra clave de Python, hay ciertas claves de diccionario que no puede permitir.


Ahora mi solución permite el acceso a los elementos del diccionario utilizando sus nombres directamente. Pero también te permite usar la “semántica del diccionario”. Aquí está el código con el uso del ejemplo:

 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 xa == x['a'] == 1 assert xbc == x['b']['c'] == 2 assert xd[1].foo == x['d'][1]['foo'] == "bar" 

Viejo Q&A, pero tengo algo más que hablar. Parece que nadie habla de dictados recursivos. Este es mi código:

 #!/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 xa == x["a"] == 1 assert xbc == x["b"]["c"] == 2 assert xbd[0] == 3 assert xbd[1] == 4 assert xbe[0][0] == 6 assert xbe[1][0] == 8 assert x[1] == 10 assert x["1"] == 11 assert x[1] != x["1"] assert id(x) == id(xbself.b.self) == id(xbself) assert xbself.a == xbself.b.self.a == 1 xx = 12 assert xx == x["x"] == 12 xy = {"a":13,"b":[14,15]} assert xya == 13 assert xyb[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 xac assert 3 in xac assert 5 in xac assert 1 in xab assert 4 in xab 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 xab assert 17 in xac if __name__ == '__main__': test01() test02() 

Puede aprovechar el módulo json de la biblioteca estándar con un enlace de objeto personalizado :

 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) 

Ejemplo de uso:

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

Y no es estrictamente de solo lectura como lo es con namedtuple , es decir, puede cambiar los valores, no la estructura:

 >>> obc = 3 >>> obc 3 

Sé que ya hay muchas respuestas aquí y llego tarde a la fiesta, pero este método recursivamente convertirá un diccionario en una estructura similar a un objeto … Funciona en 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 

Me topé con el caso que necesitaba para convertir de forma recursiva una lista de dictados en una lista de objetos, por lo que, según el fragmento de Roberto aquí, ¿qué fue lo que me funcionó?

 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 

Tenga en cuenta que cualquier tupla se convertirá a su lista equivalente, por razones obvias.

Espero que esto ayude a alguien tanto como todas sus respuestas lo hicieron por mí, muchachos.

Quería subir mi versión de este pequeño paradigma.

 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) 

Conserva los atributos para el tipo que se importa a la clase. Mi única preocupación sería sobrescribir los métodos dentro del diccionario de su análisis. ¡Pero por lo demás parece sólido!

 from mock import Mock d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]} my_data = Mock(**d) # We got # my_data.a == 1 

¿Qué hay de simplemente asignar su __dict__ al __dict__ de un objeto vacío?

 class Object: """If your dict is "flat", this is a simple way to create an object from a dict >>> obj = Object() >>> obj.__dict__ = d >>> da 1 """ pass 

Por supuesto, esto falla en su ejemplo de dictado nested, a menos que recorra el dictado de forma recursiva:

 # 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.bc 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 

Y su elemento de lista de ejemplo probablemente estaba destinado a ser un Mapping , una lista de pares (clave, valor) como este:

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

Esto también funciona bien

 class DObj(object): pass dobj = Dobj() dobj.__dict__ = {'a': 'aaa', 'b': 'bbb'} print dobj.a >>> aaa print dobj.b >>> bbb 

Aquí hay otra forma de implementar la sugerencia original de 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 

Aquí hay otra implementación:

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

[Editar] Falta un poco sobre el manejo de los dictados dentro de las listas, no solo otros dictados. Corrección añadida.

Qué tal esto:

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

Esto puede ser usado así:

 >>> o=d2o({"a" : 5, "b" : 3}) >>> print oa 5 >>> print ob 3 

Creo que un dict consta de número, cadena y dict es suficiente la mayoría del tiempo. Así que ignoro la situación de que las tuplas, listas y otros tipos no aparecen en la dimensión final de un dict.

Teniendo en cuenta la herencia, combinada con la recursión, soluciona el problema de impresión de manera conveniente y también proporciona dos formas de consultar datos, una forma de editar datos.

Vea el ejemplo a continuación, un dict que describe información sobre los estudiantes:

 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) 

Resultados:

 {'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': ''} 
 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)) 

Uso:

 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 

Aprovechando mi respuesta a ” Python: ¿Cómo agregar propiedades a una clase de forma dinámica? “:

 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 

Llama a makedata en el diccionario que desea convertir, o tal vez trydata función de lo que espera como entrada, y escupe un objeto de datos.

Notas:

  • Puede agregar elifs a trydata si necesita más funcionalidad.
  • Obviamente esto no funcionará si quieres xa = {} o similar.
  • Si desea una versión de solo lectura, use los datos de clase de la respuesta original .

Aquí hay una versión lista para anidar con nameduuple:

 from collections import namedtuple class Struct(object): def __new__(cls, data): if isinstance(data, dict): return namedtuple( 'Struct', data.iterkeys() )( *(Struct(val) for val in data.values()) ) elif isinstance(data, (tuple, list, set, frozenset)): return type(data)(Struct(_) for _ in data) else: return data 

=>

 >>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]} >>> s = Struct(d) >>> sd ['hi', Struct(foo='bar')] >>> sd[0] 'hi' >>> sd[1].foo 'bar' 

Tuve algunos problemas con __getattr__ no ser llamado, así que construí una nueva versión de clase de estilo:

 class Struct(object): '''The recursive class for building and representing objects with.''' class NoneStruct(object): def __getattribute__(*args): return Struct.NoneStruct() def __eq__(self, obj): return obj == None 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 __getattribute__(*args): try: return object.__getattribute__(*args) except: return Struct.NoneStruct() def __repr__(self): return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for (k, v) in self.__dict__.iteritems())) 

Esta versión también tiene la adición de NoneStruct que se devuelve cuando se llama a un atributo que no está establecido. Esto permite que la prueba Ninguna vea si un atributo está presente. Muy útil cuando no se conoce la entrada exacta del dict (configuración, etc.).

 bla = Struct({'a':{'b':1}}) print(bla.ab) >> 1 print(bla.ac == None) >> True