Convertir anidado Python dict a objeto?

539

Estoy buscando una manera elegante de obtener datos usando el acceso a atributos en un dict con algunos dictados y listas anidadas (es decir, sintaxis de objetos de estilo javascript).

Por ejemplo:

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

Debería ser accesible de esta manera:

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

Creo que esto no es posible sin recurrencia, pero ¿cuál sería una buena manera de obtener un estilo de objeto para los dictados?

Bagazo
fuente
66
Intenté hacer algo similar recientemente, pero una clave recurrente del diccionario ("from", que es una palabra clave de Python) me impidió seguir adelante. Porque tan pronto como intentaste usar "x.from" para acceder a ese atributo, obtendrías un error de sintaxis.
Dawie Strauss
3
eso es un problema, pero puedo abandonar "desde" para hacer la vida más fácil al acceder a grandes construcciones dict :) escribir x ['a'] ['d'] [1] ['foo'] es realmente molesto, así que xad [1]. Reglas foo. si lo necesita desde, puede acceder a él a través de getattr (x, 'desde') o usar _from como atributo en su lugar.
Marc
55
from_en lugar de _fromsegún PEP 8 .
Kos
1
Puede usar en getattr(x, 'from')lugar de renombrar el atributo.
George V. Reilly
3
La mayoría de estas "soluciones" no parecen funcionar (incluso la aceptada, no permite anidadas d1.b.c), creo que está claro que debería usar algo de una biblioteca, por ejemplo, namedtuple de colecciones , como sugiere esta respuesta , ... .
Andy Hayden

Respuestas:

660

Actualización: en Python 2.6 y versiones posteriores, considere si la namedtupleestructura de datos se adapta a sus necesidades:

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

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>
>>> s.a
1
>>> s.b
2
Eli Bendersky
fuente
19
Lo mismo aquí: esto es particularmente útil para reconstruir objetos de Python a partir de bases de datos orientadas a documentos como MongoDB.
mikemaccana
13
Para obtener una impresión más bonita, agregue: def repr __ (self): return '<% s>'% str ('\ n' .join ('% s:% s'% (k, repr (v)) para (k, v ) en sí mismo .__ dict .iteritems ()))
seis8
16
¿Funcionará con diccionarios anidados? y dicts que contienen objetos y / o listas, etc. ¿Hay capturas?
Sam Stoelinga
55
@Sam S: no creará Structs anidados a partir de diccionarios anidados, pero en general el tipo de valor puede ser cualquier cosa. La clave se limita a ser adecuada para una ranura de objeto
Eli Bendersky,
52
-1 porque a) no funciona para dicts anidados, que claramente la pregunta solicitada, y b) la misma funcionalidad actualmente existe en las bibliotecas estándar en argparse.Namespace(que también tiene definidos __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
fuente
77
¡Bueno! Sin embargo, reemplazaría .items () por .iteritems () para una huella de memoria más pequeña.
Eric O Lebigot
77
Si no es un requisito de OP, esto no es un problema, pero tenga en cuenta que esto no procesará recursivamente objetos en listas dentro de listas.
Anon
3
Me doy cuenta de que esta es una respuesta antigua, pero en estos días sería mejor usar una clase base abstracta en lugar de esa línea feaif isinstance(b, (list, tuple)):
2013
44
Consejo: Haga que la objclase herede de argparse.Namespacecaracterísticas adicionales como representación de cadena legible.
Serrano
2
Dependiendo del caso de uso, por lo general nos gustaría comprobar que es no un tipo de cadena pero que es un tipo de secuencia
wim
108

Sorprendentemente, nadie ha mencionado Bunch . Esta biblioteca está destinada exclusivamente a proporcionar acceso de estilo de atributo a objetos dictados 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)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

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

Kontinuity
fuente
16
Y hay una rama de Bunch compatible con Python 3 (parece ser 2.6-3.4) llamada Munch: github.com/Infinidat/munch
codyzu
Bunch es la mejor solución de todo esto porque es compatible con múltiples tipos de serialización y se mantiene. Genial, gracias. Desearía que la versión de python3 tuviera el mismo nombre, porque por qué no.
Jonathan
44
Así que solo una advertencia, Bunch y Attrdict también son muy lentos. Consumieron aproximadamente 1/3 y 1/2 del tiempo de mi solicitud, respectivamente, cuando vieron un uso frecuente en nuestra aplicación. Definitivamente no es algo para ignorar. Hable más sobre esto stackoverflow.com/a/31569634/364604 .
JayD3e
Si bien esto permite el acceso de objetos a los dictados, lo hace a través de getattr . Esto dificulta la introspección en REPL inteligentes como IPython.
GDorn
Curioso cómo esto se compara con JSONpath github.com/kennknowles/python-jsonpath-rw
MarkHu
64
x = type('new_dict', (object,), d)

luego agrega recursividad a esto y listo.

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)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'
SilentGhost
fuente
1
¿Por qué estás creando objetos tipográficos y no los instancia? ¿No sería eso más lógico? Quiero decir, ¿por qué no hacerlo top_instance = top()y devolver eso donde regresas top?
panqueque
66
Agradable para los datos de "hojas", pero los ejemplos convenientemente dejan de lado "ramitas" como xy x.bque vuelven feas<class '__main__.new'>
MarkHu
46

Hay un asistente de recolecció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
umbrae
fuente
28
Esto no responde a la pregunta de recursividad para los dictados anidados.
Derigible
44
no puedes modificar namedtuples.
Max
¿Podemos garantizar que el orden de la lista devuelto por las claves () y los valores () coincidan en orden? Quiero decir, si es un OrderedDict, sí. ¿Pero un dict estándar? Sé que se ordenan los dictos "compactos" de CPython 3.6+ y PyPy, pero citando la documentación: "El aspecto de preservación del orden de esta nueva implementación se considera un detalle de implementación y no se debe confiar en él"
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

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

XEye
fuente
44
Esta debería ser la respuesta. Funciona bien para anidar. Puede usar esto como un object_hook para json.load () también.
010110110101
2
Similar a la respuesta funcional de SilentGhost de 2009: los datos del nodo hoja son accesibles, pero los elementos primarios / ramitas se muestran como referencias de objetos. Para imprimir bien,def __repr__(self): return '{%s}' % str(', '.join("'%s': %s" % (k, repr(v)) for (k, v) in self.__dict__.iteritems()))
MarkHu
55
Usuarios de Python 3.x: es solo en .items()lugar de .iteritems()en la línea 4. (La función fue renombrada, pero esencialmente es la misma )
neo post moderno
Funciona perfecto para objetos anidados, gracias! Como comentario, los novatos como yo, para python3, iteritems () debe cambiarse a items ()
Óscar Andreu
31

Tomando lo que siento 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()))
andyvanee
fuente
Tenga en cuenta que el constructor se puede acortar a: def __init__(self, dct): for k, v in dct.iteritems(): setattr(self, k, isinstance(v, dict) and self.__class__(v) or v) que también elimina la llamada explícita aStruct
George V. Reilly
2
Prefiero no rechazar mi propia respuesta, pero al mirar hacia atrás, he notado que no se repite en tipos de secuencia. xd [1] .foo falla en este caso.
andyvanee
2
el isinstance(v, dict)cheque sería mejor, ya isinstance(v, collections.Mapping)que puede manejar futuras cosas como dict
hobs
30

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

import json
from collections import namedtuple

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

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

DS.
fuente
Parece ser mucho más lento que el normal .loadssi tienes un gran objeto JSON
michaelsnowden
16

Si desea acceder a las claves dict como un objeto (o como un dict para claves difíciles), hágalo de forma recursiva y también pueda actualizar el dict original, puede 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.e.f = 'g'
assert dd['e'].f == 'g'
assert d == {'c': 'd', 'e': {'f': 'g'}}
usuario1783597
fuente
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'
Luego
fuente
12

Terminé probando AMBOS el AttrDict y el Bunchbibliotecas y descubrí que eran demasiado lentas para mis usos. Después de que un amigo y yo lo investigamos, descubrimos que el método principal para escribir estas bibliotecas hace que la biblioteca recurra agresivamente a través de un objeto anidado y haga copias del objeto del diccionario en todo momento. Con esto en mente, hicimos dos cambios clave. 1) Creamos atributos con carga lenta 2) en lugar de crear copias de un objeto de diccionario, creamos copias de un objeto proxy liviano. Esta es la implementación final. El aumento del rendimiento del uso de este código es increíble. Al usar 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.5ms). Por supuesto, esto depende de tus necesidades,

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í por 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.

JayD3e
fuente
9

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

Alex Rodrigues
fuente
gracias por tu respuesta, pero ¿qué es x? el dict o un objeto estándar? ¿Podrías darme una pista por favor?
Marc
x es tu objeto. Cada objeto tiene un dict . Al actualizar el dict de un objeto, en realidad está actualizando las variables clave en él.
Alex Rodrigues
Las palabras en negrita son _ _ dict _ _
Alex Rodrigues
15
Esto no manejará diccionarios anidados.
FogleBird
No todos los objetos tienen un __dict__: prueba object().__dict__y obtendrásAttributeError: 'object' object has no attribute '__dict__'
Will Manley
8

Puede aprovechar el jsonmódulo 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)
>>> o.a
1
>>> o.b.c
2
>>> o.d[0]
u'hi'
>>> o.d[1].foo
u'bar'

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

>>> o.b.c = 3
>>> o.b.c
3
Vanni Totaro
fuente
1
La mejor respuesta con diferencia
Connor
Me gusta la idea de usar el mecanismo de carga json para elementos anidados. Pero ya tenemos un dict y no me gusta el hecho de que uno necesita crear una cadena para mapearlo en un objeto. Prefiero tener una solución que cree objetos directamente desde un dict.
Sandro
1
Primero debe convertirse a una cadena, pero es bastante genérico, ya que se podría extender a través de json.JSONEncodery object_hook.
Sandro
6

Esto debería comenzar:

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

No funciona para listas, todavía. Tendrá que ajustar las listas en una Lista de usuarios y sobrecargarlas __getitem__para ajustar los dictados.

Aaron Digulla
fuente
2
Para que funcione para las listas, use la if isinstance(d, list)cláusula de Anonla respuesta de.
Vinay Sajip
6

Sé que ya hay muchas respuestas aquí y llego tarde a la fiesta, pero este método convertirá recursivamente y 'en su lugar' un diccionario a 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
Aidan Haddon-Wright
fuente
¿Qué quiere decir "estructura similar a un objeto"?
MoralCode
1
Objeto como por el principio de tipificación de pato. Lo que quiero decir es que puede acceder a sus propiedades como lo haría si fuera una instancia de una clase. pero NO es un objeto en ese sentido, es una tupla con nombre.
Aidan Haddon-Wright el
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
adelante
fuente
4

Déjame explicarte una solución que casi utilicé 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 "from" es una palabra clave de Python, hay ciertas teclas 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 le permite usar "semántica de diccionario". Aquí está el código con ejemplo de uso:

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
fuente
1
clase Struct: def init __ (self, ** entradas): self .__ dict .update (entradas)
Kenneth Reitz
4

Preguntas y respuestas anteriores, pero tengo algo más de qué hablar. Parece que nadie habla de dict recursivo. 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 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
fuente
4

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 desde el diccionario de su análisis. ¡Pero por lo demás parece sólido!

Erik
fuente
Aunque es una buena idea, esto no parece funcionar para el ejemplo del OP, ¡de hecho parece modificar el diccionario aprobado! De hecho, df.ani siquiera funciona.
Andy Hayden
4

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
naren
fuente
3

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
robar
fuente
3

Me topé con el caso que necesitaba para convertir de forma recursiva una lista de dictados en una lista de objetos, así que basándome en el fragmento de Roberto aquí, ¿qué funcionó para mí?

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á en su lista equivalente, por razones obvias.

Espero que esto ayude a alguien tanto como todas sus respuestas me ayudaron, muchachos.

NiKo
fuente
3

¿Qué hay de solo asignarle dicta __dict__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
    >>> d.a
    1
    """
    pass

Por supuesto, esto falla en su ejemplo de dict anidado a menos que recorra el dict recursivamente:

# 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

Y su elemento de lista de ejemplo probablemente estaba destinado a ser Mappinguna 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"
placas
fuente
2

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] Se perdió un poco sobre el manejo de dictados dentro de las listas, no solo otros dictados. Se agregó una solución.

Brian
fuente
Tenga en cuenta que establecer dict en el diccionario de origen significa que cualquier cambio en los atributos del objeto resultante también afectará el diccionario que creó el objeto, y viceversa. Esto puede conducir a resultados inesperados si el diccionario se usa para otra cosa que no sea crear el objeto.
Mark Roddy
@ Mark: En realidad, cada vez se pasa un nuevo diccionario a DictObj, en lugar de simplemente pasar por el mismo objeto dict, por lo que esto no ocurrirá. Es necesario hacer esto, ya que también necesito traducir los valores dentro de un diccionario, por lo que sería imposible pasar por el objeto dict original sin mutarlo yo mismo.
Brian
Hay bastantes respuestas a esto, la respuesta aceptada no parecía ser recursiva o manejar listas. Los leí todos y este parecía el más simple a primera vista, lo probé y funcionó. Gran respuesta.
AlwaysTraining
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))

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
Paulo Freitas
fuente
2

Qué tal esto:

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

Esto se puede usar así:

>>> o=d2o({"a" : 5, "b" : 3})
>>> print o.a
5
>>> print o.b
3
en ningún
fuente
2

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

Teniendo en cuenta la herencia, combinada con la recursividad, resuelve el problema de impresión convenientemente y también proporciona dos formas de consultar datos, una forma de editarlos.

Vea el siguiente ejemplo, 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': ''}
tcpiper
fuente
2

Por lo general, desea reflejar la jerarquía dict en su objeto, pero no la lista o las tuplas que generalmente están en el nivel más bajo. Así es como hice esto:

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)

Entonces hacemos:

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

y obten:

x.b.b1.x 1

x.c ['hola', 'bar']

VengaVenga
fuente
1

Mi diccionario es de este formato:

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

Como se puede ver, he anidado diccionarios y una lista de dictados . Esto se debe a que addr_bk se decodificó a partir de los datos del búfer de protocolo que se convirtió en un dict python usando lwpb.codec. Hay un campo opcional (por ejemplo, correo electrónico => donde la clave puede no estar disponible) y un campo repetido (por ejemplo, teléfono => convertido a la lista de dict).

Probé todas las soluciones propuestas anteriormente. Algunos no manejan bien los diccionarios anidados. Otros no pueden imprimir los detalles del objeto fácilmente.

Solo la solución, dict2obj (dict) de Dawie Strauss, funciona mejor.

Lo he mejorado un poco para manejar cuando no se puede encontrar la clave:

# 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

Luego, lo probé con:

# 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
fuente
1

Partiendo de mi respuesta a " python: ¿Cómo agregar propiedades a una clase dinámicamente? ":

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

Invoca makedatael diccionario que deseas convertir, o tal vez trydatadependiendo de lo que esperas como entrada, y escupe un objeto de datos.

Notas:

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