Como posso tornar a subclasse de ditado o mais "perfeita" possível? O objetivo final é ter um ditado simples em que as chaves estejam em minúsculas.
Parece que deveria haver um pequeno conjunto de primitivas que eu posso substituir para fazer isso funcionar, mas de acordo com todas as minhas pesquisas e tentativas, parece que esse não é o caso:
Se eu substituir
__getitem__
/__setitem__
, entãoget
/set
não funcionarei. Como posso fazê-los funcionar? Certamente não preciso implementá-los individualmente?Estou impedindo que a decapagem funcione e preciso implementar
__setstate__
etc?Devo apenas usar o mutablemapping (parece que não se deve usar
UserDict
ouDictMixin
)? Se sim, como? Os documentos não são exatamente esclarecedores.
Aqui está minha primeira tentativa, get()
não funciona e, sem dúvida, existem muitos outros problemas menores:
class arbitrary_dict(dict):
"""A dictionary that applies an arbitrary key-altering function
before accessing the keys."""
def __keytransform__(self, key):
return key
# Overridden methods. List from
# /programming/2390827/how-to-properly-subclass-dict
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
# Note: I'm using dict directly, since super(dict, self) doesn't work.
# I'm not sure why, perhaps dict is not a new-style class.
def __getitem__(self, key):
return dict.__getitem__(self, self.__keytransform__(key))
def __setitem__(self, key, value):
return dict.__setitem__(self, self.__keytransform__(key), value)
def __delitem__(self, key):
return dict.__delitem__(self, self.__keytransform__(key))
def __contains__(self, key):
return dict.__contains__(self, self.__keytransform__(key))
class lcdict(arbitrary_dict):
def __keytransform__(self, key):
return str(key).lower()
fonte
Respostas:
Você pode escrever um objeto que se comporte
dict
facilmente com ABC s (Abstract Base Classes) docollections.abc
módulo. Ele ainda informa se você perdeu um método, então abaixo está a versão mínima que fecha o ABC.Você recebe alguns métodos gratuitos do ABC:
Eu não iria subclassificar
dict
(ou outros embutidos) diretamente. Geralmente não faz sentido, porque o que você realmente deseja fazer é implementar a interface de adict
. E é exatamente para isso que servem os ABCs.fonte
__keytransform__()
porque viola o guia de estilo do PEP 8, que aconselha "Nunca invente esses nomes; use-os apenas como documentado" no final da seção Descritivo: Estilos de nomeação .if isinstance(t, collections.MutableMapping): print t, "can be used like a dict"
. Não verifique o tipo de um objeto, verifique a interface.A resposta aceita seria minha primeira abordagem, mas como ela tem alguns problemas e como ninguém abordou a alternativa, subclassificou um
dict
, eu vou fazer isso aqui.O que há de errado com a resposta aceita?
Parece um pedido bastante simples para mim:
A resposta aceita não é realmente subclasse
dict
e um teste para isso falha:Idealmente, qualquer código de verificação de tipo estaria testando a interface que esperamos ou uma classe base abstrata, mas se nossos objetos de dados estiverem sendo passados para funções que estão sendo testadas
dict
- e não podemos "consertar" essas funções, esse código vai falhar.Outras queixas que se pode fazer:
fromkeys
.A resposta aceita também possui uma redundância
__dict__
- portanto, ocupa mais espaço na memória:Na verdade, subclassificação
dict
Podemos reutilizar os métodos de ditado por herança. Tudo o que precisamos fazer é criar uma camada de interface que garanta que as chaves sejam passadas para o dict em forma minúscula, se forem strings.
Bem, implementá-los individualmente é a desvantagem dessa abordagem e a vantagem de usar
MutableMapping
(veja a resposta aceita), mas na verdade não é muito mais trabalho.Primeiro, vamos fatorar a diferença entre Python 2 e 3, criar um singleton (
_RaiseKeyError
) para garantir que saibamos se realmente obtemos um argumentodict.pop
e criar uma função para garantir que nossas chaves de string sejam minúsculas:Agora implementamos - estou usando
super
os argumentos completos para que esse código funcione para Python 2 e 3:Usamos uma abordagem quase caldeira-plate para qualquer método ou método especial que faz referência a uma chave, mas caso contrário, por herança, temos métodos:
len
,clear
,items
,keys
,popitem
, evalues
de graça. Embora isso exigisse uma reflexão cuidadosa para acertar, é trivial ver que isso funciona.(Observe que
haskey
foi preterido no Python 2, removido no Python 3.)Aqui está um pouco de uso:
decapagem
E a subclasse dict pickles muito bem:
__repr__
Definimos
update
e__init__
, mas você tem uma linda__repr__
por padrão:No entanto, é bom escrever um
__repr__
para melhorar a depuração do seu código. O teste ideal éeval(repr(obj)) == obj
. Se for fácil fazer o seu código, recomendo vivamente:Veja bem, é exatamente o que precisamos para recriar um objeto equivalente - isso é algo que pode aparecer em nossos logs ou nos backtraces:
Conclusão
Sim, essas são mais algumas linhas de código, mas pretendem ser abrangentes. Minha primeira inclinação seria usar a resposta aceita e, se houvesse algum problema, analisaria a minha resposta - pois é um pouco mais complicada e não há ABC para me ajudar a acertar minha interface.
A otimização prematura está buscando maior complexidade na busca de desempenho.
MutableMapping
é mais simples - portanto, obtém uma vantagem imediata, sendo tudo o resto igual. No entanto, para mostrar todas as diferenças, vamos comparar e contrastar.Devo acrescentar que houve um esforço para colocar um dicionário semelhante no
collections
módulo, mas foi rejeitado . Você provavelmente deveria fazer isso:Deve ser muito mais facilmente depurável.
Compare e contraste
Existem 6 funções de interface implementadas com a
MutableMapping
(que está faltandofromkeys
) e 11 com adict
subclasse. Eu não preciso de implementar__iter__
ou__len__
, mas em vez disso eu tenho que implementarget
,setdefault
,pop
,update
,copy
,__contains__
, efromkeys
- mas estes são bastante trivial, desde que eu posso usar a herança para a maioria dessas implementações.Ele
MutableMapping
implementa algumas coisas em Python quedict
implementam em C - então eu esperaria que umadict
subclasse tivesse melhor desempenho em alguns casos.Temos liberdade
__eq__
nas duas abordagens - ambas assumindo igualdade apenas se outro ditado for minúsculo - mas, novamente, acho que adict
subclasse será comparada mais rapidamente.Resumo:
MutableMapping
é mais simples, com menos oportunidades de bugs, mas mais lenta, consome mais memória (veja ditado redundante) e falhaisinstance(x, dict)
dict
é mais rápida, usa menos memória e passaisinstance(x, dict)
, mas tem maior complexidade para implementar.Qual é mais perfeito? Isso depende da sua definição de perfeito.
fonte
__slots__
ou talvez reutilizá-lo__dict__
como a loja, mas isso mistura a semântica, outro ponto potencial de crítica.ensure_lower
no primeiro argumento (que é sempre a chave)? Então haveria o mesmo número de substituições, mas todas elas teriam a forma__getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)
.copy
- acho que deveria fazê-lo, não? Eu acho que deveria testar a interface - por exemplo, o objeto pandas DataFrame não é uma instância de Mapeamento (na última verificação), mas possui itens / iteritems.Meus requisitos eram um pouco mais rígidos:
Meu pensamento inicial foi substituir nossa classe Path desajeitada por uma subclasse unicode que não diferencia maiúsculas de minúsculas - mas:
some_dict[CIstr(path)]
é feio)Então eu finalmente tive que escrever aquele ditado insensível ao caso. Graças ao código do @AaronHall, que foi facilitado 10 vezes.
Implícito vs explícito ainda é um problema, mas quando a poeira baixar, renomeie os atributos / variáveis para começar com ci (e um grande e gordo comentário explicando que ci significa insensível a maiúsculas e minúsculas). Acho que é uma solução perfeita - como os leitores do código devem esteja ciente de que estamos lidando com estruturas de dados subjacentes que não diferenciam maiúsculas de minúsculas. Esperançosamente, isso corrigirá alguns bugs difíceis de reproduzir, que eu suspeito que se resumem à sensibilidade do caso.
Comentários / correções bem-vindos :)
fonte
__repr__
deve usar o da classe pai__repr__
para passar no teste eval (repr (obj)) == obj (eu não acho que funcione agora) e não confiar__str__
.total_ordering
decorador da classe - que eliminará 4 métodos da sua subclasse unicode. Mas a subclasse dict parece muito bem implementada. : PCIstr.__repr__
, no seu caso, pode passar no teste de reprovação com muito pouco aborrecimento e facilitar a depuração. Eu também adicionaria um__repr__
para o seu ditado. Vou fazer isso na minha resposta para demonstrar.__slots__
no CIstr - faz diferença no desempenho (o CIstr não deve ser subclasse ou de fato usado fora do LowerDict, deve ser uma classe final aninhada estática). Ainda não tenho certeza de como resolver elegantemente a questão repr (a picada pode conter uma combinação de'
e"
citações)Tudo o que você precisa fazer é
OU
Uma amostra de uso para meu uso pessoal
Nota : testado apenas em python3
fonte
Depois de tentar para fora ambas as top duas sugestões, eu tenha resolvido em uma rota de meia sombra de aparência para Python 2.7. Talvez 3 seja mais saudável, mas para mim:
que eu realmente odeio, mas parece atender às minhas necessidades, que são:
**my_dict
dict
, isso ignora seu código . Experimente.isinstance(my_dict, dict)
dict
Se você precisar se diferenciar dos outros, pessoalmente eu uso algo assim (embora eu recomende nomes melhores):
Contanto que você só precise se reconhecer internamente, é mais difícil chamar acidentalmente
__am_i_me
devido à troca de nomes do python (isso é renomeado para_MyDict__am_i_me
qualquer coisa que chame fora desta classe). Um pouco mais privado que_method
s, tanto na prática quanto culturalmente.Até agora, não tenho queixas, além da
__class__
substituição seriamente sombria . Eu ficaria emocionado ao ouvir qualquer problema que outras pessoas encontrem com isso, porém, não entendo completamente as consequências. Mas até agora não tive problemas, e isso me permitiu migrar muitos códigos de qualidade intermediária em vários locais sem precisar de alterações.Como evidência: https://repl.it/repls/TraumaticToughCockatoo
Basicamente: copie a opção 2 atual , adicione
print 'method_name'
linhas a todos os métodos e tente isso e observe a saída:Você verá um comportamento semelhante para outros cenários. Digamos que seu fake
dict
seja um invólucro em torno de outro tipo de dados, então não há uma maneira razoável de armazenar os dados no ditado de apoio;**your_dict
ficará vazio, independentemente do que qualquer outro método faça.Isso funciona corretamente
MutableMapping
, mas assim que você herdardict
dela se torna incontrolável.Edit: como uma atualização, isso está ocorrendo sem um único problema há quase dois anos, em várias centenas de milhares (eh, pode haver alguns milhões) de linhas de python complicado e legado. Então, eu estou muito feliz com isso :)
Edit 2: aparentemente eu copiei isso ou algo errado há muito tempo.
@classmethod __class__
não funciona paraisinstance
verificações -@property __class__
faz: https://repl.it/repls/UnitedScientificSequencefonte
**your_dict
estará vazio" (se você subclasse dedict
)? Eu não vi qualquer problema com descompactação dict ...**your_dict
não executa seu código, por isso não pode produzir nada "especial". Por exemplo, você não pode contar "leituras" porque não executa seu código de contagem de leitura. MutableMapping faz trabalho para isso (usá-lo se você pode!), Mas ele falharisinstance(..., dict)
, então eu não poderia usá-lo. yay software legado.**your_dict
, mas acho muito interessante que issoMutableMapping
seja feito.**some_dict
é bastante comum. No mínimo, isso acontece com muita frequência em decoradores; portanto, se você tiver algum , estará imediatamente em risco de um comportamento aparentemente impossível, se não o considerar.def __class__()
truque não parece funcionar com o Python 2 ou 3, pelo menos para o código de exemplo da pergunta Como registrar a implementação do abc.MutableMapping como uma subclasse dict? (modificado para funcionar de outra maneira nas duas versões). Eu queroisinstance(SpreadSheet(), dict)
retornarTrue
.