Acessando chaves de ditado como um atributo?

303

Acho mais conveniente acessar chaves dict como em obj.foovez de obj['foo'], então escrevi este trecho:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

No entanto, suponho que deve haver algum motivo pelo qual o Python não forneça essa funcionalidade imediatamente. Quais seriam as advertências e armadilhas do acesso a chaves de ditado dessa maneira?

Izz ad-Din Ruhulessin
fonte
16
Se você estiver acessando chaves codificadas em um conjunto limitado de tamanho fixo em qualquer lugar, é melhor criar objetos que as contenham. collections.namedtupleé muito útil para isso.
6
stackoverflow.com/questions/3031219/... tem uma solução semelhante, mas vai mais longe
keflavich
1
Encontrei um módulo para isso em github.com/bcj/AttrDict . Não sei como ele se compara às soluções aqui e nas questões relacionadas.
Matt Wilkie
Eu também usei hacks semelhantes, agora eu usoeasydict.EasyDict
múon
Mais maneiras de acessar membros do dicionário com um '.' : stackoverflow.com/questions/2352181/…
Pale Blue Dot

Respostas:

304

A melhor maneira de fazer isso é:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

Alguns profissionais:

  • Na verdade funciona!
  • Nenhum método de classe de dicionário é sombreado (por exemplo, .keys() funciona muito bem. A menos que, é claro, você atribua algum valor a eles, veja abaixo)
  • Atributos e itens estão sempre sincronizados
  • A tentativa de acessar a chave inexistente como um atributo aumenta corretamente em AttributeErrorvez deKeyError

Contras:

  • Métodos como .keys()a vontade não funcionar muito bem se eles se substituídos por dados de entrada
  • Causa um vazamento de memória no Python <2.7.4 / Python3 <3.2.3
  • Pylint vai bananas com E1123(unexpected-keyword-arg)eE1103(maybe-no-member)
  • Para os não iniciados, parece pura magia.

Uma breve explicação sobre como isso funciona

  • Todos os objetos python armazenam internamente seus atributos em um dicionário chamado __dict__.
  • Não há exigência de que o dicionário interno __dict__precise ser "apenas um ditado simples", para que possamos atribuir qualquer subclasse de dict()ao dicionário interno.
  • No nosso caso, simplesmente atribuímos a AttrDict()instância que estamos instanciando (como estamos __init__).
  • Ao chamar super()o __init__()método, garantimos que ele (já) se comporte exatamente como um dicionário, pois essa função chama todo o código de instanciação do dicionário .

Uma razão pela qual o Python não fornece essa funcionalidade imediatamente

Conforme observado na lista "contras", isso combina o espaço para nome das chaves armazenadas (que podem vir de dados arbitrários e / ou não confiáveis!) Com o espaço para nome dos atributos do método dict interno. Por exemplo:

d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items():    # TypeError: 'list' object is not callable
    print "Never reached!"
Kimvais
fonte
1
Você acha que o vazamento memorry ocorreria com um objeto simples como: >>> MYD classe (objeto): ... def o init __ (self, d): ... self .__ dict = d
Rafe
Causa o vazamento mesmo em 2,7
pi.
1
Faça isso <= 2.7.3, pois é isso que estou usando.
pi.
1
Nas notas de versão 2.7.4, eles mencionam que foi corrigido (não antes).
31715 Robert Siemer
1
@viveksinghggits só porque você está acessando as coisas via ., você não pode violar as regras da linguagem :) E eu não gostaria AttrDictde converter automaticamente os campos que contêm espaço em algo diferente.
Yurik
125

Você pode ter todos os caracteres de sequência legal como parte da chave se usar a notação de matriz. Por exemplo,obj['!#$%^&*()_']

Hery
fonte
1
@Izkata yes. O engraçado é que, geralmente, existe uma "pergunta principal". título e uma 'questão de fundo', talvez porque a SE não goste de ouvir "o título diz tudo"; as 'advertências' sendo a inferior aqui.
N611x007
2
Não que o JavaScript seja um exemplo particularmente bom de linguagem de programação, mas os objetos em JS suportam acesso a atributos e notação de matriz, o que permite conveniência para o caso comum e um fallback genérico para símbolos que não são nomes de atributos legais.
André Caron
@ Izkata Como isso responde à pergunta. Esta resposta diz apenas que chaves podem ter qualquer nome.
Melab
4
@ Melab A pergunta é What would be the caveats and pitfalls of accessing dict keys in this manner?(como atributos), e a resposta é que a maioria dos caracteres mostrados aqui não seria utilizável.
Izkata 18/05/19
83

De Esta outra questão SO há um grande exemplo de implementação que simplifica o código existente. E se:

class AttributeDict(dict): 
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__

Muito mais conciso e não deixa espaço para adulteração adicional de itens __getattr__e __setattr__funções no futuro.

slacy
fonte
Você poderia chamar AttributeDict.update ou AttributeDict.get usando este método?
Dor
13
Você deve ter em mente que, se você adicionar novos atributos em tempo de execução, eles não serão adicionados ao próprio dict, mas ao atributo dict . Por exemplo d = AttributeDict(foo=1). d.bar = 1o atributo bar é armazenado dentro do atributo dict, mas não no próprio dict. a impressão dmostra apenas o item foo.
P3trus
7
+1 porque funciona perfeitamente até onde eu sei. @GringoSuave, @Izkata, @ P3trus Peço a alguém que afirme que falha, mostre um exemplo que não funcione d = AttributeDict(foo=1);d.bar = 1;print d=> {'foo': 1, 'bar': 1}Funciona para mim!
23813 Dave
4
@DaveAbrahams Leia a pergunta completa e veja as respostas de Hery, Ryan e TheCommunistDuck. Não está perguntando sobre como fazer isso, mas sobre os problemas que podem surgir .
Izkata
6
Você deve fornecer um __getattr__método que gera um AttributeErrorse o atributo dado não existir, caso contrário as coisas como getattr(obj, attr, default_value)não funcionam (ou seja, não voltar default_valuese attrnão existe no obj)
jcdude
83

Em que eu respondo à pergunta que foi feita

Por que o Python não o oferece imediatamente?

Suspeito que isso tenha a ver com o Zen do Python : "Deve haver uma - e de preferência apenas uma - maneira óbvia de fazer isso". Isso criaria duas maneiras óbvias de acessar valores dos dicionários: obj['key']eobj.key .

Advertências e armadilhas

Isso inclui possível falta de clareza e confusão no código. ou seja, o seguinte pode ser confuso para outra pessoa que deseja manter seu código em uma data posterior ou mesmo para você, se você não voltar a usá-lo por um tempo. Mais uma vez, do Zen : "A legibilidade conta!"

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1

Se dé instanciado ou KEY definido ou d[KEY] atribuído longe de onde d.spamestá sendo usado, pode facilmente causar confusão sobre o que está sendo feito, pois esse não é um idioma comumente usado. Eu sei que teria o potencial de me confundir.

Além disso, se você alterar o valor da KEYseguinte forma (mas não alterar d.spam), agora obtém:

>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'

OMI, não vale o esforço.

Outros itens

Como outros observaram, você pode usar qualquer objeto hash (não apenas uma string) como uma chave de ditado. Por exemplo,

>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>> 

é legal, mas

>>> C = type('C', (object,), {(2, 3): True})
>>> d = C()
>>> assert d.(2, 3) is True
  File "<stdin>", line 1
  d.(2, 3)
    ^
SyntaxError: invalid syntax
>>> getattr(d, (2, 3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
>>> 

não é. Isso fornece acesso a todo o intervalo de caracteres imprimíveis ou outros objetos hash para suas chaves de dicionário, que você não possui ao acessar um atributo de objeto. Isso possibilita a mágica de uma metaclasse de objeto em cache, como a receita do Python Cookbook (Cap. 9) .

Em que editorializo

Eu prefiro a estética do que o spam.eggsexcesso spam['eggs'](acho que parece mais limpo), e realmente comecei a desejar essa funcionalidade quando conheci o namedtuple. Mas a conveniência de poder fazer o seguinte supera isso.

>>> KEYS = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>

Este é um exemplo simples, mas frequentemente me vejo usando dictos em situações diferentes das que usaria obj.keynotação (ou seja, quando preciso ler prefs de um arquivo XML). Em outros casos, onde sou tentado a instanciar uma classe dinâmica e a atribuir alguns atributos por razões estéticas, continuo usando um ditado de consistência para melhorar a legibilidade.

Tenho certeza que o OP há muito tempo resolveu isso para sua satisfação, mas se ele ainda deseja essa funcionalidade, sugiro que baixe um dos pacotes do pypi que o fornece:

  • Bunch é com quem eu estou mais familiarizado. Subclasse dedict, para que você tenha toda essa funcionalidade.
  • O AttrDict também parece bom demais , mas eu não estou tão familiarizado com isso e não procurei a fonte com tantos detalhes quanto o Bunch .
  • O Addict é mantido ativamente e fornece acesso semelhante a atr e muito mais.
  • Conforme observado nos comentários de Rotareti, o Bunch foi descontinuado, mas há um fork ativo chamado Munch .

No entanto, para melhorar a legibilidade de seu código, recomendo fortemente que ele não misture seus estilos de notação. Se ele preferir essa notação, ele deve simplesmente instanciar um objeto dinâmico, adicionar seus atributos desejados e chamá-lo de um dia:

>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}


Em que eu atualizo, para responder a uma pergunta de acompanhamento nos comentários

Nos comentários (abaixo), Elmo pergunta:

E se você quiser ir mais fundo? (referente ao tipo (...))

Embora eu nunca tenha usado esse caso de uso (novamente, eu tendem a usar aninhados dict, por consistência), o código a seguir funciona:

>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
...     setattr(d, x, C())
...     i = 1
...     for y in 'one two three'.split():
...         setattr(getattr(d, x), y, i)
...         i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}
Doug R.
fonte
1
Grupo está obsoleta, mas há um garfo ativa dela: github.com/Infinidat/munch
Rotareti
@Rotareti - Obrigado pelo alerta! Essa não é a funcionalidade que eu uso, então eu não sabia disso.
Doug R.
E se você quiser ir mais fundo? (referindo-se ao tipo (...))
Ole Aldric
6
Python é como um guarda-chuva invertido, sustentado por fortes chuvas. Tudo parece inteligente e descolado, depois de algum tempo começa a ficar pesado e, de repente, você lê algumas coisas de guru embutidas no SE e a coisa toda volta ao normal com toda a carga útil em seus ombros. Enquanto ainda está encharcado, você se sente mais leve e tudo fica tão claro e revigorado.
Ole Aldric
19

Você pode obter uma classe de contêiner conveniente da biblioteca padrão:

from argparse import Namespace

para evitar ter que copiar em torno dos bits de código. Não há acesso padrão ao dicionário, mas é fácil recuperá-lo, se você realmente o quiser. O código no argparse é simples,

class Namespace(_AttributeHolder):
    """Simple object for storing attributes.

    Implements equality by attribute names and values, and provides a simple
    string representation.
    """

    def __init__(self, **kwargs):
        for name in kwargs:
            setattr(self, name, kwargs[name])

    __hash__ = None

    def __eq__(self, other):
        return vars(self) == vars(other)

    def __ne__(self, other):
        return not (self == other)

    def __contains__(self, key):
        return key in self.__dict__
lindyblackburn
fonte
2
MAIS 1 para fazer referência a uma biblioteca padrão, que aborda o primeiro comentário do OP.
Gordon Bean
4
Python inclui uma classe mais rápida (implementado em C) para esse caso: types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André
18

E se você quisesse uma chave que fosse um método, como __eq__ou __getattr__?

E você não conseguiria ter uma entrada que não começasse com uma letra; portanto, usar 0343853como chave está fora.

E se você não quisesse usar uma string?

O Pato Comunista
fonte
De fato, ou por exemplo outros objetos como chaves. No entanto, eu classificaria o erro como "comportamento esperado" - com a minha pergunta, eu estava mais voltado para o inesperado.
Izz ad-Din Ruhulessin
pickle.dumpusa__getstate__
Cees Timmerman
12

tuplas podem ser usadas chaves dict. Como você acessaria a tupla em sua construção?

Além disso, namedtuple é uma estrutura conveniente que pode fornecer valores por meio do acesso ao atributo.

Senthil Kumaran
fonte
7
A desvantagem dos nomeados é que eles são imutáveis.
Izz ad-Din Ruhulessin
10
Alguns diriam que ser imutável não é um bug, mas um recurso das tuplas.
ben author
9

Que tal Prodict , a pequena classe Python que escrevi para governar todos eles :)

Além disso, você obtém a conclusão automática de código , instanciações recursivas de objetos e conversão automática de tipos !

Você pode fazer exatamente o que pediu:

p = Prodict()
p.foo = 1
p.bar = "baz"

Exemplo 1: Dica de tipo

class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

código automático completo

Exemplo 2: Conversão automática de tipo

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])

print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'>

print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.flag_colors))  # <class 'list'>
Ramazan Polat
fonte
2
instala em python2 via semente, mas não funciona em python2
Ant6n
2
@ Ant6n requer python 3.6+ causa de anotações de tipo
Ramazan Polat
8

Não funciona em geral. Nem todas as chaves dict válidas criam atributos endereçáveis ​​("a chave"). Então, você precisará ter cuidado.

Objetos Python são basicamente dicionários. Portanto, duvido que haja muito desempenho ou outra penalidade.

Tallseth
fonte
8

Isso não aborda a questão original, mas deve ser útil para pessoas que, como eu, acabam aqui quando procuram uma biblioteca que ofereça essa funcionalidade.

Addict , é uma ótima biblioteca para isso: https://github.com/mewwts/addict cuida de muitas preocupações mencionadas nas respostas anteriores.

Um exemplo dos documentos:

body = {
    'query': {
        'filtered': {
            'query': {
                'match': {'description': 'addictive'}
            },
            'filter': {
                'term': {'created_by': 'Mats'}
            }
        }
    }
}

Com viciado:

from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'
gonz
fonte
8

Eu me perguntei qual seria o estado atual das "chaves de ditado como attr" no ecossistema python. Como vários comentadores apontaram, isso provavelmente não é algo que você queira criar do zero , pois existem várias armadilhas e espingardas, algumas delas muito sutis. Além disso, eu não recomendaria usarNamespace como classe base, já estive nesse caminho, não é bonito.

Felizmente, existem vários pacotes de código aberto que fornecem essa funcionalidade, prontos para a instalação do pip! Infelizmente, existem vários pacotes. Aqui está uma sinopse, em dezembro de 2019.

Candidatos (comprometimento mais recente com o domínio | #commits | #contribs | cobertura%):

Não é mais mantida ou sub-mantida:

  • treedict (28-03-2014 | 95 | 2 |?%)
  • monte (2012-03-12 | 20 | 2 |?%)
  • NeoBunch

Atualmente, recomendo arrebentar ou viciado . Eles têm o maior número de confirmações, contribuidores e lançamentos, sugerindo uma base de código de código aberto saudável para cada um. Eles possuem o leia-me.md mais bonito, 100% de cobertura e um conjunto de testes de boa aparência.

Eu não tenho um cachorro nesta corrida (por enquanto!), Além de ter rodado meu próprio código de dict / attr e perdido uma tonelada de tempo porque eu não estava ciente de todas essas opções :). Posso contribuir para viciar / arrebentar no futuro, pois prefiro ver um pacote sólido do que um monte de pacotes fragmentados. Se você gosta deles, contribua! Em particular, parece que munch poderia usar um distintivo codecov e o viciado poderia usar um distintivo da versão python.

profissionais viciados:

  • inicial recursiva (foo.abc = 'bar'), argumentos do tipo dict tornam-se addict.Dict

contras do viciado:

  • sombras typing.Dictse vocêfrom addict import Dict
  • Nenhuma verificação de chave. Devido a permitir init recursivo, se você digitar incorretamente uma chave, basta criar um novo atributo, em vez de KeyError (obrigado AljoSt)

munch profissionais:

  • nomeação única
  • funções ser / de integradas para JSON e YAML

munch contras:

  • nenhum init recursivo / somente pode iniciar um atributo por vez

Em que editorializo

Muitas luas atrás, quando eu usava editores de texto para escrever python, em projetos somente comigo ou com outro desenvolvedor, eu gostava do estilo dict-attrs, da capacidade de inserir chaves apenas declarando foo.bar.spam = eggs. Agora, trabalho em equipes e uso um IDE para tudo, e me afastei desses tipos de estruturas de dados e da digitação dinâmica em geral, em favor da análise estática, técnicas funcionais e dicas de tipo. Comecei a experimentar essa técnica, subclassificando o Pstruct com objetos de meu próprio design:

class  BasePstruct(dict):
    def __getattr__(self, name):
        if name in self.__slots__:
            return self[name]
        return self.__getattribute__(name)

    def __setattr__(self, key, value):
        if key in self.__slots__:
            self[key] = value
            return
        if key in type(self).__dict__:
            self[key] = value
            return
        raise AttributeError(
            "type object '{}' has no attribute '{}'".format(type(self).__name__, key))


class FooPstruct(BasePstruct):
    __slots__ = ['foo', 'bar']

Isso fornece um objeto que ainda se comporta como um ditado, mas também permite acessar chaves como atributos, de uma maneira muito mais rígida. A vantagem aqui é que eu (ou os infelizes consumidores do seu código) sei exatamente quais campos podem ou não existir, e o IDE pode preencher automaticamente os campos. Subclassing vanilla também dictsignifica que a serialização do json é fácil. Eu acho que a próxima evolução nessa idéia seria um gerador de protobuf personalizado que emite essas interfaces, e um bom argumento é que você obtém estruturas de dados em várias linguagens e IPC via gRPC de graça.

Se você optar por atribuir atributos, é essencial documentar quais campos são esperados, para sua própria sanidade (e de seus colegas de equipe).

Sinta-se livre para editar / atualizar este post para mantê-lo recente!

DeusXMachina
fonte
2
Uma grande vantagem addicté que ele não gera exceções quando você digita um atributo incorretamente, pois retornará um novo Dict(isso é necessário para que foo.abc = 'bar' funcione).
AljoSt 27/01
5

Aqui está um pequeno exemplo de registros imutáveis ​​usando o built-in collections.namedtuple:

def record(name, d):
    return namedtuple(name, d.keys())(**d)

e um exemplo de uso:

rec = record('Model', {
    'train_op': train_op,
    'loss': loss,
})

print rec.loss(..)
Ben Usman
fonte
5

Apenas para adicionar alguma variedade à resposta, o sci-kit learn implementou isso como Bunch:

class Bunch(dict):                                                              
    """ Scikit Learn's container object                                         

    Dictionary-like object that exposes its keys as attributes.                 
    >>> b = Bunch(a=1, b=2)                                                     
    >>> b['b']                                                                  
    2                                                                           
    >>> b.b                                                                     
    2                                                                           
    >>> b.c = 6                                                                 
    >>> b['c']                                                                  
    6                                                                           
    """                                                                         

    def __init__(self, **kwargs):                                               
        super(Bunch, self).__init__(kwargs)                                     

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

    def __dir__(self):                                                          
        return self.keys()                                                      

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

    def __setstate__(self, state):                                              
        pass                       

Tudo o que você precisa é obter os métodos setattre getattr- as getattrverificações de chaves de ditado e a verificação de atributos reais. Esta setstaeté uma correção para correção de "cachos" de decapagem / remoção de selos - se estiver novamente incorreto, verifique https://github.com/scikit-learn/scikit-learn/issues/6196

user2804865
fonte
3

Não há necessidade de escrever seus próprios já que setattr () e getattr () já existem.

A vantagem dos objetos de classe provavelmente entra em jogo na definição e herança de classes.

David W
fonte
3

Eu criei isso com base na entrada deste segmento. Porém, eu preciso usar odict, então tive que substituir get e set attr. Eu acho que isso deve funcionar para a maioria dos usos especiais.

O uso fica assim:

# Create an ordered dict normally...
>>> od = OrderedAttrDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od
OrderedAttrDict([('a', 1), ('b', 2)])

# Get and set data using attribute access...
>>> od.a
1
>>> od.b = 20
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])

# Setting a NEW attribute only creates it on the instance, not the dict...
>>> od.c = 8
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
>>> od.c
8

A classe:

class OrderedAttrDict(odict.OrderedDict):
    """
    Constructs an odict.OrderedDict with attribute access to data.

    Setting a NEW attribute only creates it on the instance, not the dict.
    Setting an attribute that is a key in the data will set the dict data but 
    will not create a new instance attribute
    """
    def __getattr__(self, attr):
        """
        Try to get the data. If attr is not a key, fall-back and get the attr
        """
        if self.has_key(attr):
            return super(OrderedAttrDict, self).__getitem__(attr)
        else:
            return super(OrderedAttrDict, self).__getattr__(attr)


    def __setattr__(self, attr, value):
        """
        Try to set the data. If attr is not a key, fall-back and set the attr
        """
        if self.has_key(attr):
            super(OrderedAttrDict, self).__setitem__(attr, value)
        else:
            super(OrderedAttrDict, self).__setattr__(attr, value)

Esse é um padrão bem legal já mencionado no thread, mas se você quiser apenas pegar um ditado e convertê-lo em um objeto que funcione com o preenchimento automático em um IDE, etc:

class ObjectFromDict(object):
    def __init__(self, d):
        self.__dict__ = d
Rafe
fonte
3

Aparentemente, agora existe uma biblioteca para isso - https://pypi.python.org/pypi/attrdict - que implementa essa funcionalidade exata, mais mesclagem recursiva e carregamento de json. Pode valer a pena dar uma olhada.

Yurik
fonte
3

É isso que eu uso

args = {
        'batch_size': 32,
        'workers': 4,
        'train_dir': 'train',
        'val_dir': 'val',
        'lr': 1e-3,
        'momentum': 0.9,
        'weight_decay': 1e-4
    }
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)

print (args.lr)
mujjiga
fonte
Esta é uma boa resposta rápida e suja. Minha única observação / comentário é que eu acho que o construtor namedtuple aceitará uma lista de strings, então sua solução pode ser simplificado (eu acho) para:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen
2

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!'
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
1
Observe que ele pode sombrear dictmétodos, por exemplo: m=Map(); m["keys"] = 42; m.keys()give TypeError: 'int' object is not callable.
precisa saber é o seguinte
@bfontaine A idéia é ser um tipo field/attributee não um method, mas se você atribuir um método a um número, poderá acessar esse método m.method().
epool 23/09/16
2

Deixe-me postar outra implementação, que se baseia na resposta do Kinvais, mas integra idéias do AttributeDict proposto em http://databio.org/posts/python_AttributeDict.html .

A vantagem desta versão é que ela também funciona para dicionários aninhados:

class AttrDict(dict):
    """
    A class to convert a nested Dictionary into an object with key-values
    that are accessible using attribute notation (AttrDict.attribute) instead of
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
    """

    # Inspired by:
    # http://stackoverflow.com/a/14620633/1551810
    # http://databio.org/posts/python_AttributeDict.html

    def __init__(self, iterable, **kwargs):
        super(AttrDict, self).__init__(iterable, **kwargs)
        for key, value in iterable.items():
            if isinstance(value, dict):
                self.__dict__[key] = AttrDict(value)
            else:
                self.__dict__[key] = value
kadee
fonte
1
class AttrDict(dict):

     def __init__(self):
           self.__dict__ = self

if __name__ == '____main__':

     d = AttrDict()
     d['ray'] = 'hope'
     d.sun = 'shine'  >>> Now we can use this . notation
     print d['ray']
     print d.sun
h_vm
fonte
1

A solução é:

DICT_RESERVED_KEYS = vars(dict).keys()


class SmartDict(dict):
    """
    A Dict which is accessible via attribute dot notation
    """
    def __init__(self, *args, **kwargs):
        """
        :param args: multiple dicts ({}, {}, ..)
        :param kwargs: arbitrary keys='value'

        If ``keyerror=False`` is passed then not found attributes will
        always return None.
        """
        super(SmartDict, self).__init__()
        self['__keyerror'] = kwargs.pop('keyerror', True)
        [self.update(arg) for arg in args if isinstance(arg, dict)]
        self.update(kwargs)

    def __getattr__(self, attr):
        if attr not in DICT_RESERVED_KEYS:
            if self['__keyerror']:
                return self[attr]
            else:
                return self.get(attr)
        return getattr(self, attr)

    def __setattr__(self, key, value):
        if key in DICT_RESERVED_KEYS:
            raise AttributeError("You cannot set a reserved name as attribute")
        self.__setitem__(key, value)

    def __copy__(self):
        return self.__class__(self)

    def copy(self):
        return self.__copy__()
Bruno Rocha - rochacbruno
fonte
1

Quais seriam as advertências e armadilhas do acesso a chaves de ditado dessa maneira?

Como o @Henry sugere, um motivo pelo qual o acesso pontilhado não pode ser usado nos dict é que ele limita os nomes das chaves do dict a variáveis ​​válidas para python, restringindo, assim, todos os nomes possíveis.

A seguir, exemplos de por que o acesso pontilhado não seria útil em geral, dado um ditado d:

Validade

Os seguintes atributos seriam inválidos no Python:

d.1_foo                           # enumerated names
d./bar                            # path names
d.21.7, d.12:30                   # decimals, time
d.""                              # empty strings
d.john doe, d.denny's             # spaces, misc punctuation 
d.3 * x                           # expressions  

Estilo

As convenções do PEP8 imporiam uma restrição leve à nomeação de atributos:

A. Nomes de palavras-chave reservadas (ou funções internas):

d.in
d.False, d.True
d.max, d.min
d.sum
d.id

Se o nome de um argumento de função colidir com uma palavra-chave reservada, geralmente é melhor acrescentar um único sublinhado à direita ...

B. A regra de caso sobre métodos e nomes de variáveis :

Os nomes de variáveis ​​seguem a mesma convenção que os nomes de funções.

d.Firstname
d.Country

Use as regras de nomeação de funções: minúsculas com palavras separadas por sublinhados, conforme necessário, para melhorar a legibilidade.


Às vezes, essas preocupações são levantadas em bibliotecas como pandas , o que permite o acesso pontilhado das colunas DataFrame pelo nome. O mecanismo padrão para resolver restrições de nomenclatura também é uma notação de matriz - uma string entre colchetes.

Se essas restrições não se aplicarem ao seu caso de uso, há várias opções nas estruturas de dados de acesso pontilhado .

pylang
fonte
1

Você pode usar o dict_to_obj https://pypi.org/project/dict-to-obj/ Ele faz exatamente o que você pediu

From dict_to_obj import DictToObj
a = {
'foo': True
}
b = DictToObj(a)
b.foo
True
Alon Barad
fonte
1
É uma boa forma de colocar .ideae qualquer arquivo específico do usuário ou gerado pelo IDE no seu .gitignore.
DeusXMachina
1

Esta não é uma resposta 'boa', mas achei que era bacana (não lida com ditados aninhados na forma atual). Simplesmente envolva seu ditado em uma função:

def make_funcdict(d=None, **kwargs)
    def funcdict(d=None, **kwargs):
        if d is not None:
            funcdict.__dict__.update(d)
        funcdict.__dict__.update(kwargs)
        return funcdict.__dict__
    funcdict(d, **kwargs)
    return funcdict

Agora você tem uma sintaxe um pouco diferente. Para acessar os itens de ditado como os atributos f.key. Para acessar os itens de dict (e outros métodos de dict) da maneira usual, f()['key']podemos atualizar convenientemente o dict chamando f com argumentos de palavra-chave e / ou um dicionário

Exemplo

d = {'name':'Henry', 'age':31}
d = make_funcdict(d)
>>> for key in d():
...     print key
... 
age
name
>>> print d.name
... Henry
>>> print d.age
... 31
>>> d({'Height':'5-11'}, Job='Carpenter')
... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'}

E aí está. Ficarei feliz se alguém sugerir vantagens e desvantagens desse método.

DylanYoung
fonte
0

Conforme observado por Doug, há um pacote Bunch que você pode usar para obter a obj.keyfuncionalidade. Na verdade, há uma versão mais nova chamada

NeoBunch

Ele possui um ótimo recurso que converte seu ditado em um objeto NeoBunch através de sua função neobunchify . Eu uso muito os modelos Mako e a transmissão de dados como objetos do NeoBunch os torna muito mais legíveis; portanto, se você acabar usando um ditado normal no seu programa Python, mas quiser a notação de pontos em um modelo Mako, poderá usá-lo dessa maneira:

from mako.template import Template
from neobunch import neobunchify

mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
    out_file.write(mako_template.render(**neobunchify(data)))

E o modelo Mako pode se parecer com:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor
mizu
fonte
O link para o NeoBunch é 404
DeusXMachina
0

A maneira mais fácil é definir uma classe, vamos chamá-la de espaço para nome. que usa o objeto dict .update () no dict. Então, o ditado será tratado como um objeto.

class Namespace(object):
    '''
    helps referencing object in a dictionary as dict.key instead of dict['key']
    '''
    def __init__(self, adict):
        self.__dict__.update(adict)



Person = Namespace({'name': 'ahmed',
                     'age': 30}) #--> added for edge_cls


print(Person.name)
Elmahy
fonte