Uma classe python que atua como dict

113

Quero escrever uma classe personalizada que se comporte assim dict- então, estou herdando de dict.

Minha pergunta, porém, é: Preciso criar um dictmembro privado em meu __init__()método? Não vejo sentido nisso, já que tenho o dictcomportamento de simplesmente herdar de dict.

Alguém pode apontar por que a maioria dos snippets de herança se parece com o abaixo?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict = {} 

   # other methods follow

Em vez do mais simples ...

class CustomDictTwo(dict):
   def __init__(self):
      # initialize my other stuff here ...

   # other methods follow

Na verdade, acho que suspeito que a resposta à pergunta seja para que os usuários não possam acessar diretamente o seu dicionário (ou seja, eles precisam usar os métodos de acesso que você forneceu).

No entanto, e quanto ao operador de acesso à matriz []? Como alguém implementaria isso? Até agora, não vi um exemplo que mostra como substituir o []operador.

Portanto, se uma []função de acesso não for fornecida na classe personalizada, os métodos de base herdados estarão operando em um dicionário diferente?

Tentei o seguinte snippet para testar minha compreensão da herança do Python:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(self, id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)
print md[id]

Recebi o seguinte erro:

KeyError: <id da função integrada>

O que há de errado com o código acima?

Como faço para corrigir a classe myDictpara poder escrever um código como este?

md = myDict()
md['id'] = 123

[Editar]

Eu editei o exemplo de código acima para me livrar do erro bobo que cometi antes de sair correndo de minha mesa. Foi um erro de digitação (eu deveria ter percebido na mensagem de erro).

skyeagle
fonte

Respostas:

105
class Mapping(dict):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

    def __getitem__(self, key):
        return self.__dict__[key]

    def __repr__(self):
        return repr(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

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

    def clear(self):
        return self.__dict__.clear()

    def copy(self):
        return self.__dict__.copy()

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

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

    def values(self):
        return self.__dict__.values()

    def items(self):
        return self.__dict__.items()

    def pop(self, *args):
        return self.__dict__.pop(*args)

    def __cmp__(self, dict_):
        return self.__cmp__(self.__dict__, dict_)

    def __contains__(self, item):
        return item in self.__dict__

    def __iter__(self):
        return iter(self.__dict__)

    def __unicode__(self):
        return unicode(repr(self.__dict__))


o = Mapping()
o.foo = "bar"
o['lumberjack'] = 'foo'
o.update({'a': 'b'}, c=44)
print 'lumberjack' in o
print o

In [187]: run mapping.py
True
{'a': 'b', 'lumberjack': 'foo', 'foo': 'bar', 'c': 44}
Ricky Wilson
fonte
37
Se você for criar uma subclasse dict, deverá usar o próprio objeto (using super) em vez de simplesmente delegar à instância __dict__- o que significa essencialmente que você está criando dois dicts para cada instância.
Aaron Hall
8
self .__ dict__ não é o mesmo que o conteúdo real do dicionário. Todo objeto Python, independente de seu tipo, possui um _dict__que contém todos os atributos do objeto (métodos, campos, etc). Você não quer mexer com isso, a menos que queira escrever um código que está se modificando ...
Raik
86

Como isso

class CustomDictOne(dict):
   def __init__(self,*arg,**kw):
      super(CustomDictOne, self).__init__(*arg, **kw)

Agora você pode usar as funções integradas, como dict.get()as self.get().

Você não precisa embrulhar um oculto self._dict. Sua aula já é um dict.

S.Lott
fonte
3
Este. Não faz sentido herdar dictsem chamar seu construtor primeiro.
Sykora,
1
Observe que seu herdado dictcontém, na verdade, 2 instâncias de dict: a 1ª é o contêiner herdado e a 2ª é o dicionário que contém os atributos de classe - você pode evitar isso usando slots .
ankostis
1
Na __dict__verdade, o é criado apenas quando é acessado pela primeira vez, portanto, desde que os usuários não tentem usá-lo, tudo bem. __slots__seria bom embora.
Aaron Hall
9
Use espaços após as vírgulas, seu selvagem! ;-)
James Burke
10

Para fins de integridade, aqui está o link para a documentação mencionada por @ björn-pollex para o Python 2.x mais recente (2.7.7 no momento da escrita):

Emulando tipos de contêineres

(Desculpe por não usar a função de comentários, só não tenho permissão para fazer isso por stackoverflow.)

he1ix
fonte
3

O problema com este pedaço de código:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)

... é que o seu método 'adicionar' (... e qualquer método que você deseja que seja membro de uma classe) precisa ter um 'self' explícito declarado como seu primeiro argumento, como:

def add(self, 'id', 23):

Para implementar a sobrecarga do operador para acessar itens por chave, procure na documentação os métodos mágicos __getitem__e __setitem__.

Observe que, como o Python usa Duck Typing, pode não haver razão para derivar sua classe dict personalizada da classe dict da linguagem - sem saber mais sobre o que está tentando fazer (por exemplo, se você precisa passar uma instância deste classe em algum código em algum lugar que irá quebrar, a menos que isinstance(MyDict(), dict) == True), você pode ser melhor apenas implementar a API que torna sua classe suficientemente semelhante a dict e parar por aí.

bgporter
fonte
3

Aqui está uma solução alternativa:

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

a = AttrDict()
a.a = 1
a.b = 2
张小诚
fonte
Isso é ruim porque você não define nenhum método personalizado e também há outros problemas, como disse outra resposta.
Shital Shah
isso é exatamente o que eu precisava. Obrigado!
jakebrinkmann
2

Eu realmente não vejo a resposta certa para isso em lugar nenhum

class MyClass(dict):
    
    def __init__(self, a_property):
        self[a_property] = a_property

Tudo o que você realmente precisa fazer é definir o seu próprio __init__- isso realmente é tudo o que existe.

Outro exemplo (um pouco mais complexo):

class MyClass(dict):

    def __init__(self, planet):
        self[planet] = planet
        info = self.do_something_that_returns_a_dict()
        if info:
            for k, v in info.items():
                self[k] = v

    def do_something_that_returns_a_dict(self):
        return {"mercury": "venus", "mars": "jupiter"}

Este último exemplo é útil quando você deseja incorporar algum tipo de lógica.

Enfim ... em suma, class GiveYourClassAName(dict)é o suficiente para fazer sua classe agir como um dit. Qualquer operação de comando que você fizer selfserá como um comando normal.

Danny Meijer
fonte
1

Esta é minha melhor solução. Eu usei isso muitas vezes.

class DictLikeClass:
    ...
    def __getitem__(self, key):
        return getattr(self, key)

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

Você pode usar como:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])
Madogan
fonte
0

Não herde do dicionário interno do Python, nunca! por exemplo, o updatemétodo não seria usado __setitem__, eles fazem muito para otimização. Use UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass
user2674414
fonte
1
Com base no que alguém nunca deve herdar do dicionário integrado? Da documentação ( docs.python.org/3/library/collections.html#collections.UserDict ): "A necessidade dessa classe foi parcialmente suplantada pela capacidade de criar subclasses diretamente de dict; no entanto, essa classe pode ser mais fácil de trabalhar porque o dicionário subjacente está acessível como um atributo. " Também na mesma página: O módulo de coleções está "Obsoleto desde a versão 3.3, será removido na versão 3.9: Classes de base abstratas de coleções movidas para o módulo coleções.abc." ... que não tem UserDict.
NumesSanguis
Eu suspeito que isso é inspirado por, ou pelo menos ressoa com, treyhunner.com/2019/04/…
tripleee