Como usar um ponto "." acessar membros do dicionário?

282

Como faço para tornar os membros do dicionário Python acessíveis através de um ponto "."?

Por exemplo, em vez de escrever mydict['val'], eu gostaria de escrever mydict.val.

Também gostaria de acessar dict aninhados dessa maneira. Por exemplo

mydict.mydict2.val 

se referiria a

mydict = { 'mydict2': { 'val': ... } }
bodacydo
fonte
20
Muitas das situações em que as pessoas usam dictos aninhados seriam tão bem ou melhor servidas por dictos com tuplas quanto chaves, onde d[a][b][c]é substituído por d[a, b, c].
Mike Graham
7
Não é mágico: foo = {}; foo [1,2,3] = "um, dois, três!"; foo.keys () => [(1,2,3)]
Bryan Oakley
10
Uau. Uau de novo. Eu não sabia que as tuplas poderiam ser as chaves do ditado. Uau terceira vez.
bodacydo
3
Qualquer objeto "hashable" pode ser usado como a chave de um ditado. A maioria dos objetos imutáveis ​​também é lavável, mas apenas se todo o seu conteúdo for lavável. O código d [1, 2, 3] funciona porque "," é o "crie um operador de tupla"; é o mesmo que d [(1, 2, 3)]. Os parênteses geralmente são opcionais na declaração de uma tupla.
Larry Hastings
6
Você já considerou o caso em que a chave possui um ponto por si só - {"my.key":"value"}? Ou quando a chave é uma palavra-chave, como "de"? Já o considerei algumas vezes, e são mais problemas e solução de problemas do que benefícios percebidos.
Todor Minakov

Respostas:

147

Você pode fazer isso usando essa classe que acabei de criar. Com essa classe, você pode usar o Mapobjeto como outro dicionário (incluindo serialização json) ou com a notação de ponto. Espero ajudá-lo:

class Map(dict):
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v

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

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

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

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

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

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

Exemplos de uso:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
# Or
m['new_key'] = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']
epool
fonte
21
Para trabalhar em Python 3, atualizei .iteritems()para.items()
berto
13
Observe que isso se comportará de maneira diferente das expectativas comuns, pois não aumentará AttributeErrorse o atributo não existir. Em vez disso, ele retornará None.
mic_e
Recomenda a adição de getstate e setstate para que cópias profundas e outros sistemas possam suportá-lo.
user1363990
4
Você pode simplificar seu construtor para self.update(*args,**kwargs). Além disso, você pode adicionar __missing__(self,key): value=self[key]= type(self)(); return value. Em seguida, você pode adicionar entradas ausentes usando a notação de ponto. Se você quiser que ele seja selecionável, você pode adicionar __getstate__e__setstate__
Jens Munk
1
Isto faria com que hasattr(Map, 'anystring') is true. which means the hasattr would always return True due to overriding __getattr__`
Xiao
264

Eu sempre mantive isso em um arquivo util. Você pode usá-lo como um mixin em suas próprias classes também.

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

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

mydict.nested = dotdict(nested_dict)
mydict.nested.val
# 'nested works too'
derek73
fonte
5
Resposta muito simples, ótimo! Você sabe o que eu preciso fazer para que o preenchimento de tabulação funcione no IPython? A classe precisaria implementar __dir __ (self), mas de alguma forma não consigo fazê-la funcionar.
andreas-h
8
+1 por simplicidade. mas parece não funcionar em dict aninhados. d = {'foo': {'bar': 'baz'}}; d = dotdict(d); d.foo.barlança um erro de atributo, mas d.foofunciona bem.
tmthyjames
2
Sim, isso não funciona para estruturas aninhadas complexas.
David
16
@tmthyjames você poderia simplesmente retornar tipo dotdict objeto no método getter para o acesso de forma recursiva atributos com a notação de ponto como: python class DotDict(dict): """dot.notation access to dictionary attributes""" def __getattr__(*args): val = dict.get(*args) return DotDict(val) if type(val) is dict else val __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__
TMKasun
4
Depois de experimentar com ele, ao que parece geté realmente uma má ideia, uma vez que vai voltar Noneem vez de elevar um erro para itens em falta ...
NichtJens
117

Instalar dotmapviapip

pip install dotmap

Ele faz tudo o que você quer que faça e subclasses dict, para que funcione como um dicionário normal:

from dotmap import DotMap

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

Além disso, você pode convertê-lo para e de dictobjetos:

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

Isso significa que, se algo que você deseja acessar já está em dictforma, você pode transformá-lo em um DotMappara facilitar o acesso:

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

Por fim, ele cria automaticamente novas DotMapinstâncias filho para que você possa fazer coisas assim:

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

Comparação com Bunch

Divulgação completa: Sou o criador do DotMap . Eu o criei porque Bunchestavam faltando esses recursos

  • lembrando que os itens do pedido foram adicionados e iterando nessa ordem
  • DotMapcriação automática de filhos , que economiza tempo e cria um código mais limpo quando você tem muita hierarquia
  • construindo a partir de uma dictconversão recursiva de todas as dictinstâncias filhas paraDotMap
Chris Redford
fonte
2
:-) você pode fazê-lo funcionar com chaves que já possuem ponto no nome? {"test.foo": "bar"}pode ser acessado via mymap.test.fooIsso seria fantástico. Levará alguma regressão para converter um mapa plano em um mapa profundo e aplicar o DotMap a ele, mas vale a pena!
dlite922
Arrumado. Alguma maneira de fazer a listagem / conclusão de guias funcionar com as teclas do bloco de anotações Jupyter? O acesso no estilo de pontos é mais valioso para uso interativo.
Dmitri
@Dmitri Cool product. Nunca ouvi falar disso antes, por isso não sei como fazer o preenchimento automático funcionar. Concordo que o uso DotMapcom preenchimento automático funciona melhor. Uso Sublime Text, que preenche automaticamente as palavras-chave digitadas anteriormente.
Chris Redford
1
Acho que falta extração de dicionário para coisas como **kwargsou c = {**a, **b}. De fato, falha silenciosamente, se comporta como um dicionário vazio ao extrair.
Simon Streicher
@SimonStreicher Eu testei isso m = DotMap(); m.a = 2; m.b = 3; print('{a} {b}'.format(**m));e obtive o esperado 2 3. Se você tem um caso quebrado comprovado que funciona para dict()mas não DotMap(), envie seu código para a guia Problemas no GitHub.
Chris Redford
56

Derivar do ditado ee implementar __getattr__e __setattr__.

Ou você pode usar o Bunch, que é muito semelhante.

Eu não acho que é possível monkeypatch classe dict interno.

Kugel
fonte
2
O que significa monkeypatch exatamente? Eu ouvi sobre isso, mas não usado. (Desculpe por fazer essas perguntas para iniciantes, ainda não sou muito bom com programação (sou apenas aluno do 2º ano)) #
bodacydo 28/02/10
9
O monkeypatching está usando a dinamicidade do Python (ou qualquer outra linguagem) para alterar algo que normalmente seria definido no código-fonte. Isso se aplica especialmente à alteração da definição de classes depois que elas são criadas.
Mike Graham
Se você estiver usando muito essa funcionalidade, tenha cuidado com a velocidade do Bunch. Eu o estava usando com bastante frequência e acabou consumindo um terço do meu tempo de solicitação. Confira minha resposta para uma explicação mais detalhada disso.
21415 JayD3e
22

O Fabric possui uma implementação realmente agradável e mínima . Estendendo isso para permitir acesso aninhado, podemos usar defaultdictae o resultado é algo como isto:

from collections import defaultdict

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

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

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

Faça uso da seguinte maneira:

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

Isso explica um pouco a resposta de Kugel de "Derivar do ditado ee implementar __getattr__e __setattr__". Agora você sabe como!

Dave
fonte
1
Aquele é incrível!
Thomas Klinger
É bom incluir um ditado padrão - no entanto, isso parece funcionar apenas ao iniciar um ditado do zero. Se precisarmos converter um dict existente em um "dotdict" recursivamente. Aqui está uma alternativa dotdictque permite converter dictobjetos existentes recursivamente: gist.github.com/miku/…
miku
19

Eu tentei isso:

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

você pode tentar __getattribute__também.

faça de cada dict um tipo de dotdict seria bom o suficiente; se você deseja iniciar isso a partir de um dict de várias camadas, tente implementar __init__também.

tdihp
fonte
oops, a resposta de @Kugel é semelhante.
tdihp
1
tdihp, ainda gosto da sua resposta porque a compreendi mais rapidamente - ela possui o código real.
yigal 29/01
1
+1 para o código real. Mas a sugestão de @ Kugel de usar o Bunch também é muito boa.
Dannid 24/10
Achei que seria útil para incorporar isso dentro de uma função, colocando def docdict(name):antes e depois `se isinstance (nome, dict): retorno DotDict (nome) Nome do retorno`
Daniel Moskovich
ótimo exemplo simples .. Eu estendi isso um pouco para que um dict aninhado seja facilmente encadeado, semelhante a @DanielMoskovich, mas também retorne nós de folha corretamente para int, string, etc ... ou null se não for encontradoclass dotdict(dict): def __getattr__(self, name): if name not in self: return None elif type(self[name]) is dict: return JsonDot(self[name]) else: return self[name]
D Sievers
11

Não. O acesso a atributos e a indexação são coisas separadas no Python, e você não deve querer que elas executem o mesmo. Faça uma classe (possivelmente uma feita por namedtuple) se você tiver algo que deva ter atributos acessíveis e use []notação para obter um item de um ditado.

Mike Graham
fonte
Obrigado pela resposta. Mas dê uma olhada nesta pergunta que também acabei de fazer: stackoverflow.com/questions/2352252/… Parece uma boa idéia usar em .vez de []acessar estruturas de dados complicadas nos modelos Mako.
bodacydo
2
Eu posso ver um caso de uso para isso; na verdade, eu fiz isso apenas algumas semanas atrás. No meu caso, eu queria um objeto que pudesse acessar atributos com notação de ponto. Achei muito fácil simplesmente herdar do dict, para incorporar todos os recursos do dict, mas a interface pública para esse objeto usa a notação de ponto (é essencialmente uma interface somente leitura para alguns dados estáticos). Meus usuários estão muito mais felizes com 'foo.bar' do que com 'foo ["bar"]' e estou feliz por poder usar os recursos do tipo de dados dict.
Bryan Oakley
10
Você já conhece um bom estilo Python: estamos dizendo, não finja que os valores de um ditado são atributos. É uma prática ruim. Por exemplo, e se você deseja armazenar um valor com o mesmo nome que um atributo existente de um ditado, como "itens" ou "obter" ou "pop"? Provavelmente algo confuso. Então não faça isso!
Larry Hastings
5
Ops, esqueci-me de atributos como 'itens', 'get' ou 'pop. Obrigado por trazer esse exemplo importante!
precisa saber é
5
@ Gabe, faz muito tempo ... mas acho que vale a pena dizer. Não é "bom o suficiente em JS": é "horrível o suficiente em JS". Fica engraçado quando você armazena chaves / attr que têm o mesmo nome que outros atributos importantes na cadeia prototípica.
precisa saber é o seguinte
11

Se você deseja selecionar seu dicionário modificado, precisará adicionar alguns métodos de estado às respostas acima:

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

    def __getstate__(self):
        return self

    def __setstate__(self, state):
        self.update(state)
        self.__dict__ = self
volodymyr
fonte
Obrigado pelo comentário sobre decapagem. Fiquei louco por esse erro e só percebi que era por causa desse problema!
Shagru
Também acontece quando você usa copy.deepcopy. Essa adição é necessária.
user1363990
Simplificação:__getattr__ = dict.get
martineau
9

Com base na resposta de Kugel e levando em consideração as palavras de cautela de Mike Graham, e se fizermos um invólucro?

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

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

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

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

  """

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

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

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

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

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

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

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

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

Use SimpleNamespace:

>>> from types import SimpleNamespace   
>>> d = dict(x=[1, 2], y=['a', 'b'])
>>> ns = SimpleNamespace(**d)
>>> ns.x
[1, 2]
>>> ns
namespace(x=[1, 2], y=['a', 'b'])
Dmitry Zotikov
fonte
1
Essa abordagem funciona melhor. (com json carregado do arquivo)
ged
Isso explica ditados aninhados?
Mojimi 29/01
6

Gosto do Munch e oferece muitas opções úteis sobre o acesso a pontos.

importação arrebentar

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

dict_munch = munch.munchify (temp_1)

dict_munch.person.fname

Senthil
fonte
6

Recentemente, me deparei com a biblioteca ' Box ', que faz a mesma coisa.

Comando de instalação: pip install python-box

Exemplo:

from box import Box

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

print(mydict.key1.v1)

Eu achei que era mais eficaz do que outras bibliotecas existentes, como o dotmap, que geram erro de recursão em python quando você tem dict aninhados grandes.

link para a biblioteca e detalhes: https://pypi.org/project/python-box/

Pradip Gupta
fonte
5

Use __getattr__, muito simples, funciona em Python 3.4.3

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


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

Resultado:

10000
StackOverflow
IRSHAD
fonte
4

O idioma em si não suporta isso, mas às vezes isso ainda é um requisito útil. Além da receita do Bunch, você também pode escrever um pequeno método que pode acessar um dicionário usando uma string pontilhada:

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

que suportaria algo como isto:

>> test_dict = {'thing': {'spam': 12, 'foo': {'cheeze': 'bar'}}}
>> output = get_var(test_dict, 'thing.spam.foo.cheeze')
>> print output
'bar'
>>
pbanka
fonte
4

Para basear-se na resposta do epool, esta versão permite acessar qualquer dict interno através do operador dot:

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

Por exemplo, foo.bar.baz[1].babaretorna "loo".

class Map(dict):
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    if isinstance(v, dict):
                        v = Map(v)
                    if isinstance(v, list):
                        self.__convert(v)
                    self[k] = v

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

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

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

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

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

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

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]
toque meu corpo
fonte
1
Python 3: substituir iteritems()por items()e xrange()comrange()
sasawatc 22/01
3
def dict_to_object(dick):
    # http://stackoverflow.com/a/1305663/968442

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

    return Struct(**dick)

Se alguém decidir convertê-lo permanentemente dictem objeto, isso deve fazer. Você pode criar um objeto descartável antes de acessar.

d = dict_to_object(d)
nehem
fonte
def attr (** kwargs): o = lambda: Nenhum o .__ dict __. update (** kwargs) return o
throws_exceptions_at_you
2

Eu acabei tentando AMBOS o AttrDict e o Bunchbibliotecas e achei que elas eram uma maneira lenta de usar meus usos. Depois que um amigo e eu analisamos, 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 carregados preguiçosamente 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 consumiram apenas 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). Obviamente, isso depende das suas necessidades, mas se você estiver usando essa funcionalidade bastante no seu código,

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.

JayD3e
fonte
0

Eu gostaria de jogar minha própria solução no ringue:

https://github.com/skorokithakis/jsane

Ele permite analisar o JSON em algo que você pode acessar with.attribute.lookups.like.this.r(), principalmente porque eu não tinha visto essa resposta antes de começar a trabalhar nela.

Stavros Korokithakis
fonte
Python é culpado de alguns erros de design simples irritantes, aumentar KeyErroré um deles. Quando alguém acessa a chave que não existe, tudo o que precisa fazer é retornarNone semelhante ao comportamento do JS. Sou um grande fã de autovivificação tanto para leitura quanto para escrita. Sua biblioteca está mais próxima do ideal.
Nehem
0

Não é uma resposta direta à pergunta do OP, mas inspirada e talvez útil para alguns. Criei uma solução baseada em objeto usando o interno __dict__(código de maneira alguma otimizado)

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


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

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

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

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

    """

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

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

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

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

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


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

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


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

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


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

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

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


# Tests

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

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

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

Uma maneira simples de obter acesso a pontos (mas não a matriz) é usar um objeto simples no Python. Como isso:

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

... e use-o assim:

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

... para convertê-lo em um ditado:

>>> print(obj.__dict__)
{"key": "value"}
Emil Stenström
fonte
0

Essa solução é um refinamento da proposta pelo epool para atender ao requisito do OP de acessar dicts aninhados de maneira consistente. A solução por epool não permitiu acessar dicts aninhados.

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


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

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

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

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

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

Com esta classe, pode-se agora fazer algo como: A.B.C.D.

deepak
fonte
0

Isso também funciona com dicts aninhados e garante que os dices anexados posteriormente se comportem da mesma maneira:

class DotDict(dict):

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

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

    __setattr__ = __setitem__
    __getattr__ = dict.__getitem__
Yaniv K.
fonte
0

A resposta de @ derek73 é muito elegante, mas não pode ser decapada nem copiada (em profundidade) e retorna Nonepara as chaves ausentes. O código abaixo corrige isso.

Editar: eu não vi a resposta acima que aborda exatamente o mesmo ponto (votado). Estou deixando a resposta aqui para referência.

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

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)
Marnix
fonte
-1

Uma solução meio delicada

class DotDict(dict):

    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __getattr__(self, key):

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

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

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

            return candidate

        return typer(dict.get(self, key))
Yonks Somarl
fonte