Converter dict Python aninhado em objeto?


539

Estou procurando uma maneira elegante de obter dados usando o acesso a atributos em um dict com alguns dict e listas aninhados (ou seja, sintaxe de objeto no estilo javascript).

Por exemplo:

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

Deve ser acessível desta maneira:

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

Eu acho que isso não é possível sem recursão, mas qual seria uma boa maneira de obter um estilo de objeto para ditados?


6
Eu estava tentando fazer algo semelhante recentemente, mas uma chave de dicionário recorrente ("from" - que é uma palavra-chave Python) me impediu de continuar com ela. Porque assim que você tentava usar "x.from" para acessar esse atributo, você recebia um erro de sintaxe.
Dawie Strauss 20/08/09

3
isso é realmente um problema, mas posso abandonar o "from" para facilitar a vida ao acessar grandes construções de ditado :) digitar x ['a'] ['d'] [1] ['foo'] é realmente irritante, então xad [1] regras. se você precisar, poderá acessá-lo via getattr (x, 'from') ou usar _from como atributo.
Marc

5
from_e não de _fromacordo com o PEP 8 .
Kos

1
Você pode usar em getattr(x, 'from')vez de renomear o atributo.
George V. Reilly

3
A maioria dessas "soluções" parece não funcionar (mesmo a aceita, não permite aninhados d1.b.c), acho claro que você deve usar algo de uma biblioteca, por exemplo, nomeado o dobro de coleções , como sugere esta resposta . .
Andy Hayden

Respostas:


660

Atualização: No Python 2.6 em diante, considere se a namedtupleestrutura de dados atende às suas necessidades:

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

A alternativa (conteúdo original da resposta) é:

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

Então, você pode usar:

>>> args = {'a': 1, 'b': 2}
>>> s = Struct(**args)
>>> s
<__main__.Struct instance at 0x01D6A738>
>>> s.a
1
>>> s.b
2

19
O mesmo aqui - isso é particularmente útil para reconstruir objetos Python a partir de bancos de dados orientados a documentos como o MongoDB.
Mikemaccana

13
Para obter uma impressão mais bonita, adicione: def repr __ (self): retorne '<% s>'% str ('\ n' .join ('% s:% s'% (k, repr (v)) para (k, v ) em self .__ dict .iteritems ()))
six8

16
Isso funcionará com dicionários aninhados? e ditados contendo objetos e ou lista etc. Existem capturas?
Sam Stoelinga

5
@ Sam S: não criará Structs aninhados a partir de dicionários aninhados, mas em geral o tipo do valor pode ser qualquer coisa. Chave está restrita a ser adequado para uma ranhura objeto
Eli Bendersky

52
-1 porque: a) ela não funciona para dicts aninhadas, que a causa claramente pediu, e b) a mesma funcionalidade existe actualmente em bibliotecas padrão em argparse.Namespace(que também tem 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'

7
Boa! Eu substituiria .items () por .iteritems (), no entanto, por um menor espaço de memória.
Eric O Lebigot

7
Se não for um requisito de OP, isso não é um problema - mas observe que isso não processará recursivamente objetos em listas dentro de listas.
Anon

3
Sei que esta é uma resposta de idade, mas nos dias de hoje seria melhor usar uma classe base abstrata , em vez dessa linha feioif isinstance(b, (list, tuple)):
Wim

4
Dica: Herde a objclasse de argparse.Namespacepara recursos adicionais, como representação de seqüência de caracteres legível.
Serrano

2
Dependendo do caso de uso, normalmente iríamos verificar se é não um tipo de cadeia, mas que é um tipo de Sequência
wim

108

Surpreendentemente, ninguém mencionou Bunch . Essa biblioteca destina-se exclusivamente a fornecer acesso no estilo de atributo a objetos dict e faz exatamente o que o OP deseja. Uma demonstração:

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

Uma biblioteca Python 3 está disponível em https://github.com/Infinidat/munch - O crédito vai para codyzu


16
E há um Python 3 compatíveis (parece ser 2,6-3,4) filial do grupo chamado Munch: github.com/Infinidat/munch
codyzu

O Bunch é a melhor solução de todas, porque suporta bem vários tipos de serialização e é mantido. Ótimo, obrigado. Eu gostaria que a versão python3 tivesse o mesmo nome, porque não.
9136 Jonathan

4
Portanto, apenas um aviso, Bunch e Attrdict também são muito lentos. Eles consumiram cerca de 1/3 e 1/2 do meu tempo de solicitação, respectivamente, quando viram uso frequente em nosso aplicativo. Definitivamente não é algo a ser ignorado. Fale mais sobre isso stackoverflow.com/a/31569634/364604 .
JayD3e

Embora isso permita acesso semelhante a objetos a dictos, ele o faz via getattr . Isso dificulta a introspecção em REPLs inteligentes como o IPython.
GDorn

Curioso como este se compara a JSONpath github.com/kennknowles/python-jsonpath-rw
MarkHu

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

adicione recursão a isso e pronto.

editar é assim que eu o implementaria:

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

1
Por que você está criando objetos de tipo e não os instanciando? Isso não seria mais lógico? Quero dizer, por que não fazer top_instance = top()e retornar para onde você volta top?
panqueca

6
Bom para os dados da "folha", mas os exemplos convenientemente deixam de fora os "galhos" xe os x.bque retornam feios #<class '__main__.new'>
MarkHu

46

Existe um auxiliar de coleta chamado namedtuple, que pode fazer isso por você:

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

28
Isso não responde à questão da recursão para os ditados aninhados.
Derigible

4
você não pode modificar os nomeados.
Max

Podemos garantir que a ordem da lista retornada por chaves () e valores () corresponda em ordem? Quero dizer, se é um OrderedDict, sim. Mas um ditado padrão? Eu sei que os ditados CPython 3.6+ e PyPy "compactos" são ordenados, mas citando a documentação: "O aspecto de preservação de pedidos dessa nova implementação é considerado um detalhe da implementação e não deve ser considerado"
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

Pode ser usado com qualquer estrutura de sequência / ditado / valor de qualquer profundidade.


4
Essa deve ser a resposta. Funciona bem para aninhar. Você pode usar isso como um object_hook para json.load () também.
010110110101

2
Semelhante à resposta funcional do SilentGhost de 2009 - os dados do nó folha são acessíveis, mas os pais / galhos são exibidos como referências a objetos. Para pretty-print,def __repr__(self): return '{%s}' % str(', '.join("'%s': %s" % (k, repr(v)) for (k, v) in self.__dict__.iteritems()))
MarkHu

5
Usuários 3.x do Python: É apenas .items()em vez de .iteritems()na linha 4. (A função foi renomeado, mas faz essencialmente a mesma )
neo Post Modern

Funciona perfeito para objetos aninhados, obrigado! Como comentário novatos como eu, por iteritems python3 () deve ser alterado para items ()
Óscar Andreu

31

Tomando o que considero os melhores aspectos dos exemplos anteriores, eis o que eu criei:

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

Observe que o construtor pode ser reduzido para: o def __init__(self, dct): for k, v in dct.iteritems(): setattr(self, k, isinstance(v, dict) and self.__class__(v) or v) que também remove a chamada explícita paraStruct
George V. Reilly

2
Prefiro não votar em minha própria resposta, mas, olhando para trás, percebi que ela não se repete em tipos de sequência. xd [1] .foo falha neste caso.
andyvanee

2
o isinstance(v, dict)cheque seria melhor como isinstance(v, collections.Mapping)para que ele possa lidar com futuro dict-like coisas
fogão

29

Se seu ditado é proveniente json.loads(), você pode transformá-lo em um objeto (em vez de um ditado) em uma linha:

import json
from collections import namedtuple

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

Consulte também Como converter dados JSON em um objeto Python .


Parece ser muito mais lento do que o normal .loadsse você tem um enorme objeto JSON
michaelsnowden

16

Se você deseja acessar chaves dict como um objeto (ou dict para chaves difíceis), faça-o recursivamente e também possa atualizar o dict original, você pode:

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)

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

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'

12

Eu acabei tentando AMBOS o AttrDict e o Bunchbibliotecas e as achei muito lentas para meus usos. Depois que um amigo e eu examinamos, descobrimos que o método principal para escrever essas bibliotecas resulta na recorrência agressiva da biblioteca através de um objeto aninhado e na criação de cópias do objeto de dicionário. Com isso em mente, fizemos duas mudanças importantes. 1) Criamos atributos com carregamento lento 2) em vez de criar cópias de um objeto de dicionário, criamos cópias de um objeto proxy leve. Esta é a implementação final. O aumento de desempenho do uso desse código é incrível. Ao usar o AttrDict ou o Bunch, essas duas bibliotecas por si só consumiram 1/2 e 1/3, respectivamente, do meu tempo de solicitação (o que !?). Esse código reduziu esse tempo para quase nada (em algum lugar na faixa de 0,5ms). Isso, é claro, depende das suas necessidades,

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

Veja a implementação original aqui em https://stackoverflow.com/users/704327/michael-merickel .

A outra coisa a observar é que essa implementação é bastante simples e não implementa todos os métodos que você pode precisar. Você precisará gravá-las conforme necessário nos objetos DictProxy ou ListProxy.


9

x.__dict__.update(d) deve fazer bem.


obrigado pela sua resposta, mas o que é x? o ditado ou um objeto padrão? você poderia me dar uma dica, por favor.
Marc

x é seu objeto. Todo objeto tem um ditado . Ao atualizar o ditado de um objeto, você está realmente atualizando os principais variáveis ​​nele.
Alex Rodrigues

As palavras em negrito são _ _ dict _ _
Alex Rodrigues

15
Isso não manipula dicionários aninhados.
FogleBird 20/08/09

Não cada objeto tem um __dict__: tentativa object().__dict__e você vai terAttributeError: 'object' object has no attribute '__dict__'
Will Manley

8

Você pode aproveitar o jsonmódulo da biblioteca padrão com um gancho 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)

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

E não é estritamente somente leitura como é namedtuple, ou seja, você pode alterar valores - não estrutura:

>>> o.b.c = 3
>>> o.b.c
3

1
Melhor resposta de longe
Connor

Eu gosto da ideia de usar o mecanismo de carregamento json para elementos aninhados. Mas já temos um ditado e eu não gosto do fato de que é preciso criar uma string para mapeá-lo em um objeto. Eu gostaria de ter uma solução que crie objetos diretamente de um ditado.
Sandro

1
Precisa converter para string primeiro, mas é bastante genérico, pois é possível estendê-lo via json.JSONEncodere object_hook.
Sandro

6

Isso deve começar:

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

Ainda não funciona para listas. Você precisará agrupar as listas em uma UserList e sobrecarregar __getitem__para agrupar os dict.


2
Para fazê-lo funcionar para listas, use a if isinstance(d, list)cláusula da Anonresposta.
Vinay Sajip 20/08/09

6

Eu sei que já existem muitas respostas aqui e estou atrasado para a festa, mas esse método recursivamente e 'no lugar' converterá um dicionário em uma estrutura semelhante a objeto ... Funciona no 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

O que você quer dizer com "estrutura semelhante a objeto"?
MoralCode 2/08

1
Tipo de objeto por causa do princípio de digitação de pato. O que quero dizer é que você pode acessar suas propriedades como faria se fosse uma instância de uma classe. mas NÃO é um objeto nesse sentido, é uma tupla nomeada.
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

4

Deixe-me explicar uma solução que quase usei há algum tempo. Mas primeiro, a razão pela qual não o fiz é ilustrada pelo fato de o seguinte código:

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

print x.from

dá este erro:

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

Como "from" é uma palavra-chave Python, existem certas chaves de dicionário que você não pode permitir.


Agora, minha solução permite o acesso aos itens do dicionário usando seus nomes diretamente. Mas também permite que você use "semântica de dicionário". Aqui está o código com o exemplo 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"

1
classe Struct: def o init __ (self, ** entradas): self .__ dict .Update (entradas)
Kenneth Reitz

4

Perguntas e respostas antigas, mas tenho mais alguma coisa para conversar. Parece que ninguém fala sobre ditado recursivo. Este é o meu 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()

4

Queria fazer upload da minha versão deste pequeno 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)

Ele preserva os atributos para o tipo que é importado para a classe. Minha única preocupação seria substituir métodos de dentro do dicionário que você analisou. Mas por outro lado parece sólido!


Embora seja uma boa idéia, isso não parece funcionar para o exemplo do OP, na verdade, parece modificar o passado no dicionário! De fato, df.anem funciona.
21813 Andy Hayden

4

Isso também funciona bem também

class DObj(object):
    pass

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

print dobj.a
>>> aaa
print dobj.b
>>> bbb

3

Aqui está outra maneira de implementar a sugestão original do 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

3

Eu me deparei com o caso de que precisava converter recursivamente uma lista de dictos em lista de objetos; portanto, com base no snippet de Roberto aqui, o que funcionou para mim:

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

Observe que qualquer tupla será convertida em sua lista equivalente, por razões óbvias.

Espero que isso ajude alguém tanto quanto todas as suas respostas fizeram por mim, pessoal.


3

Que tal apenas atribuir o seu dictao __dict__objeto vazio?

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

Obviamente, isso falhará no exemplo de dict aninhado, a menos que você siga o 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

E seu elemento de lista de exemplo provavelmente deveria ser um Mapping, uma lista de pares (chave, valor) como este:

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

2

Aqui está outra implementação:

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 pouco sobre também lidar com dict dentro de listas, não apenas em outros dict. Correção adicionada.


Observe que definir dict no dicionário de origem significa que qualquer alteração nos atributos no objeto resultante também afetará o dicionário que criou o objeto e vice-versa. Isso pode levar a resultados inesperados se o dicionário for usado para algo diferente de criar o objeto.
Mark Roddy

@ Mark: Na verdade, um novo dicionário está sendo passado para o DictObj toda vez, em vez de apenas passar pelo mesmo objeto de ditado, para que isso não ocorra. É necessário fazer isso, pois também preciso traduzir os valores dentro de um dicionário, para que seja impossível passar pelo objeto dict original sem que eu o mude.
25711 Brian Brian

Existem algumas respostas para isso, a resposta aceita não parecia ser recursiva ou manipular listas. Eu li todos eles e este parecia o mais simples à primeira vista, eu testei e funcionou. Ótima resposta.
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

2

Que tal agora:

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

Isso pode ser usado assim:

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

2

Eu acho que um ditado consiste em número, string e ditado é suficiente na maioria das vezes. Portanto, ignoro a situação em que tuplas, listas e outros tipos não aparecem na dimensão final de um ditado.

Considerando a herança, combinada com a recursão, resolve o problema de impressão de maneira conveniente e também fornece duas maneiras de consultar dados, uma maneira de editar os dados.

Veja o exemplo abaixo, um ditado que descreve algumas informações sobre os alunos:

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

2

Normalmente, você deseja espelhar a hierarquia de ditados no seu objeto, mas não a lista ou as tuplas que estão normalmente no nível mais baixo. Então é assim que eu fiz isso:

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)

Então fazemos:

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

e pegue:

x.b.b1.x 1

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


1

Meu dicionário é deste formato:

addr_bk = {
    'person': [
        {'name': 'Andrew', 'id': 123, 'email': 'andrew@mailserver.com',
         'phone': [{'type': 2, 'number': '633311122'},
                   {'type': 0, 'number': '97788665'}]
        },
        {'name': 'Tom', 'id': 456,
         'phone': [{'type': 0, 'number': '91122334'}]}, 
        {'name': 'Jack', 'id': 7788, 'email': 'jack@gmail.com'}
    ]
}

Como pode ser visto, aninhei dicionários e lista de ditados . Isso ocorre porque o addr_bk foi decodificado a partir dos dados do buffer do protocolo que foram convertidos em um dict python usando lwpb.codec. Existem campos opcionais (por exemplo, email => onde a chave pode estar indisponível) e campos repetidos (por exemplo, telefone => convertidos em lista de ditados).

Eu tentei todas as soluções propostas acima. Alguns não lidam bem com os dicionários aninhados. Outros não podem imprimir os detalhes do objeto facilmente.

Somente a solução, dict2obj (dict) de Dawie Strauss, funciona melhor.

Eu o aprimorei um pouco para lidar quando a chave não pode ser encontrada:

# 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

Então, eu testei com:

# 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

1

Criando minha resposta para " python: como adicionar propriedade a uma classe dinamicamente? ":

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

Você chama makedatao dicionário que deseja converter, ou talvez trydatadependendo do que espera como entrada, e ele cospe um objeto de dados.

Notas:

  • Você pode adicionar elifs trydatase precisar de mais funcionalidade.
  • Obviamente, isso não funcionará se você quiser x.a = {}ou algo semelhante.
  • Se você deseja uma versão somente leitura, use os dados da classe da resposta original .
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.