Como "perfeitamente" substituir um ditado?

218

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ão get/ setnã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?

  • Eu preciso repr, updatee__init__ ?

  • Devo apenas usar o mutablemapping (parece que não se deve usar UserDict ou DictMixin)? 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()
Paul Biggar
fonte
Eu acho que __keytransform __ () deve ser estático. Boa abordagem embora. (@staticmethod antecedendo)
Aiyion.Prime

Respostas:

229

Você pode escrever um objeto que se comporte dictfacilmente com ABC s (Abstract Base Classes) do collections.abcmódulo. Ele ainda informa se você perdeu um método, então abaixo está a versão mínima que fecha o ABC.

from collections.abc import MutableMapping


class TransformedDict(MutableMapping):
    """A dictionary that applies an arbitrary key-altering
       function before accessing the keys"""

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.update(dict(*args, **kwargs))  # use the free update to set keys

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

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

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

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

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

    def __keytransform__(self, key):
        return key

Você recebe alguns métodos gratuitos do ABC:

class MyTransformedDict(TransformedDict):

    def __keytransform__(self, key):
        return key.lower()


s = MyTransformedDict([('Test', 'test')])

assert s.get('TEST') is s['test']   # free get
assert 'TeSt' in s                  # free __contains__
                                    # free setdefault, __eq__, and so on

import pickle
# works too since we just use a normal dict
assert pickle.loads(pickle.dumps(s)) == s

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.

Jochen Ritzel
fonte
46
Eu sugeriria renomear __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 .
martineau
1
Pergunta: embora a implementação dessa interface com um tipo definido pelo usuário geralmente resulte em operações mais lentas do tipo ditado, usando o tipo interno?
twneale
2
Existe uma maneira de fazer isso para que isinstance (_, dict) == True? Ou você apenas usa o Mapeamento Mutável para construir a subclasse?
Andy Hayden
5
@ AndyHayden: Você deve escrever if isinstance(t, collections.MutableMapping): print t, "can be used like a dict". Não verifique o tipo de um objeto, verifique a interface.
Jochen Ritzel 23/03
2
@NeilG Isto infelizmente inclui o JSONEncoder no python biblioteca padrão - github.com/python-git/python/blob/...
Andy Smith
97

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.

  • Se eu substituir __getitem__/ __setitem__, obter / definir não funcionará. Como faço para fazê-los funcionar? Certamente não preciso implementá-los individualmente?

  • Estou impedindo que a decapagem funcione e preciso implementar __setstate__etc?

  • Preciso de repr, atualização e __init__?

  • Devo apenas usar mutablemapping(parece que não se deve usar UserDict ou DictMixin)? Se sim, como? Os documentos não são exatamente esclarecedores.

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:

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.

A resposta aceita não é realmente subclasse dicte um teste para isso falha:

>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False

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:

  • A resposta aceita também está faltando o classmethod: fromkeys.
  • A resposta aceita também possui uma redundância __dict__- portanto, ocupa mais espaço na memória:

    >>> s.foo = 'bar'
    >>> s.__dict__
    {'foo': 'bar', 'store': {'test': 'test'}}

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.

Se eu substituir __getitem__/ __setitem__, obter / definir não funcionará. Como faço para fazê-los funcionar? Certamente não preciso implementá-los individualmente?

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 argumento dict.pope criar uma função para garantir que nossas chaves de string sejam minúsculas:

from itertools import chain
try:              # Python 2
    str_base = basestring
    items = 'iteritems'
except NameError: # Python 3
    str_base = str, bytes, bytearray
    items = 'items'

_RaiseKeyError = object() # singleton for no-default behavior

def ensure_lower(maybe_str):
    """dict keys can be any hashable object - only call lower if str"""
    return maybe_str.lower() if isinstance(maybe_str, str_base) else maybe_str

Agora implementamos - estou usando superos argumentos completos para que esse código funcione para Python 2 e 3:

class LowerDict(dict):  # dicts take a mapping or iterable as their optional first argument
    __slots__ = () # no __dict__ - that would be redundant
    @staticmethod # because this doesn't make sense as a global function.
    def _process_args(mapping=(), **kwargs):
        if hasattr(mapping, items):
            mapping = getattr(mapping, items)()
        return ((ensure_lower(k), v) for k, v in chain(mapping, getattr(kwargs, items)()))
    def __init__(self, mapping=(), **kwargs):
        super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
    def __getitem__(self, k):
        return super(LowerDict, self).__getitem__(ensure_lower(k))
    def __setitem__(self, k, v):
        return super(LowerDict, self).__setitem__(ensure_lower(k), v)
    def __delitem__(self, k):
        return super(LowerDict, self).__delitem__(ensure_lower(k))
    def get(self, k, default=None):
        return super(LowerDict, self).get(ensure_lower(k), default)
    def setdefault(self, k, default=None):
        return super(LowerDict, self).setdefault(ensure_lower(k), default)
    def pop(self, k, v=_RaiseKeyError):
        if v is _RaiseKeyError:
            return super(LowerDict, self).pop(ensure_lower(k))
        return super(LowerDict, self).pop(ensure_lower(k), v)
    def update(self, mapping=(), **kwargs):
        super(LowerDict, self).update(self._process_args(mapping, **kwargs))
    def __contains__(self, k):
        return super(LowerDict, self).__contains__(ensure_lower(k))
    def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
        return type(self)(self)
    @classmethod
    def fromkeys(cls, keys, v=None):
        return super(LowerDict, cls).fromkeys((ensure_lower(k) for k in keys), v)
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__())

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, e valuesde graça. Embora isso exigisse uma reflexão cuidadosa para acertar, é trivial ver que isso funciona.

(Observe que haskeyfoi preterido no Python 2, removido no Python 3.)

Aqui está um pouco de uso:

>>> ld = LowerDict(dict(foo='bar'))
>>> ld['FOO']
'bar'
>>> ld['foo']
'bar'
>>> ld.pop('FoO')
'bar'
>>> ld.setdefault('Foo')
>>> ld
{'foo': None}
>>> ld.get('Bar')
>>> ld.setdefault('Bar')
>>> ld
{'bar': None, 'foo': None}
>>> ld.popitem()
('bar', None)

Estou impedindo que a decapagem funcione e preciso implementar __setstate__etc?

decapagem

E a subclasse dict pickles muito bem:

>>> import pickle
>>> pickle.dumps(ld)
b'\x80\x03c__main__\nLowerDict\nq\x00)\x81q\x01X\x03\x00\x00\x00fooq\x02Ns.'
>>> pickle.loads(pickle.dumps(ld))
{'foo': None}
>>> type(pickle.loads(pickle.dumps(ld)))
<class '__main__.LowerDict'>

__repr__

Preciso de repr, atualização e __init__?

Definimos updatee __init__, mas você tem uma linda __repr__por padrão:

>>> ld # without __repr__ defined for the class, we get this
{'foo': None}

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:

>>> ld = LowerDict({})
>>> eval(repr(ld)) == ld
True
>>> ld = LowerDict(dict(a=1, b=2, c=3))
>>> eval(repr(ld)) == ld
True

Veja bem, é exatamente o que precisamos para recriar um objeto equivalente - isso é algo que pode aparecer em nossos logs ou nos backtraces:

>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})

Conclusão

Devo apenas usar mutablemapping(parece que não se deve usar UserDict ou DictMixin)? Se sim, como? Os documentos não são exatamente esclarecedores.

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 collectionsmódulo, mas foi rejeitado . Você provavelmente deveria fazer isso:

my_dict[transform(key)]

Deve ser muito mais facilmente depurável.

Compare e contraste

Existem 6 funções de interface implementadas com a MutableMapping(que está faltando fromkeys) e 11 com a dictsubclasse. Eu não preciso de implementar __iter__ou __len__, mas em vez disso eu tenho que implementar get, 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 MutableMappingimplementa algumas coisas em Python que dictimplementam em C - então eu esperaria que uma dictsubclasse 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 a dictsubclasse será comparada mais rapidamente.

Resumo:

  • a subclasse MutableMappingé mais simples, com menos oportunidades de bugs, mas mais lenta, consome mais memória (veja ditado redundante) e falhaisinstance(x, dict)
  • a subclasse dicté mais rápida, usa menos memória e passa isinstance(x, dict), mas tem maior complexidade para implementar.

Qual é mais perfeito? Isso depende da sua definição de perfeito.

Aaron Hall
fonte
Como a resposta aceita removeria o ditado redundante?
precisa saber é o seguinte
1
Duas maneiras que vêm à mente imediatamente são: declarar o atributo da loja __slots__ou talvez reutilizá-lo __dict__como a loja, mas isso mistura a semântica, outro ponto potencial de crítica.
Aaron Hall
1
Não seria mais fácil escrever um decorador que use um método e use o seu ensure_lowerno 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__).
Graipher 17/03/19
1
Obrigado por isso - recebendo avisos para pop e fromkeys de que eles não correspondem à assinatura do método da classe base.
Mr_and_Mrs_D
1
@Mr_and_Mrs_D Adicionei uma implementação de 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.
Aaron Hall
4

Meus requisitos eram um pouco mais rígidos:

  • Eu tive que manter as informações do caso (as strings são os caminhos para os arquivos exibidos para o usuário, mas é um aplicativo do Windows, portanto, internamente todas as operações devem fazer distinção entre maiúsculas e minúsculas)
  • Eu precisava que as chaves fossem o menor possível (isso fez diferença no desempenho da memória, cortou 110 mb de 370). Isso significava que o cache da versão em minúscula das chaves não é uma opção.
  • Eu precisava que a criação das estruturas de dados fosse o mais rápida possível (novamente fiz a diferença no desempenho, agilize desta vez). Eu tive que ir com um builtin

Meu pensamento inicial foi substituir nossa classe Path desajeitada por uma subclasse unicode que não diferencia maiúsculas de minúsculas - mas:

  • provou ser difícil acertar isso - veja: Uma classe de string que não diferencia maiúsculas de minúsculas em python
  • Acontece que o manuseio explícito de chaves de ditado torna o código detalhado e confuso - e propenso a erros (as estruturas são passadas de um lado para outro, e não está claro se elas têm instâncias CIStr como chaves / elementos, fácil de esquecer e 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.

class CIstr(unicode):
    """See https://stackoverflow.com/a/43122305/281545, especially for inlines"""
    __slots__ = () # does make a difference in memory performance

    #--Hash/Compare
    def __hash__(self):
        return hash(self.lower())
    def __eq__(self, other):
        if isinstance(other, CIstr):
            return self.lower() == other.lower()
        return NotImplemented
    def __ne__(self, other):
        if isinstance(other, CIstr):
            return self.lower() != other.lower()
        return NotImplemented
    def __lt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() < other.lower()
        return NotImplemented
    def __ge__(self, other):
        if isinstance(other, CIstr):
            return self.lower() >= other.lower()
        return NotImplemented
    def __gt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() > other.lower()
        return NotImplemented
    def __le__(self, other):
        if isinstance(other, CIstr):
            return self.lower() <= other.lower()
        return NotImplemented
    #--repr
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(CIstr, self).__repr__())

def _ci_str(maybe_str):
    """dict keys can be any hashable object - only call CIstr if str"""
    return CIstr(maybe_str) if isinstance(maybe_str, basestring) else maybe_str

class LowerDict(dict):
    """Dictionary that transforms its keys to CIstr instances.
    Adapted from: https://stackoverflow.com/a/39375731/281545
    """
    __slots__ = () # no __dict__ - that would be redundant

    @staticmethod # because this doesn't make sense as a global function.
    def _process_args(mapping=(), **kwargs):
        if hasattr(mapping, 'iteritems'):
            mapping = getattr(mapping, 'iteritems')()
        return ((_ci_str(k), v) for k, v in
                chain(mapping, getattr(kwargs, 'iteritems')()))
    def __init__(self, mapping=(), **kwargs):
        # dicts take a mapping or iterable as their optional first argument
        super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
    def __getitem__(self, k):
        return super(LowerDict, self).__getitem__(_ci_str(k))
    def __setitem__(self, k, v):
        return super(LowerDict, self).__setitem__(_ci_str(k), v)
    def __delitem__(self, k):
        return super(LowerDict, self).__delitem__(_ci_str(k))
    def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
        return type(self)(self)
    def get(self, k, default=None):
        return super(LowerDict, self).get(_ci_str(k), default)
    def setdefault(self, k, default=None):
        return super(LowerDict, self).setdefault(_ci_str(k), default)
    __no_default = object()
    def pop(self, k, v=__no_default):
        if v is LowerDict.__no_default:
            # super will raise KeyError if no default and key does not exist
            return super(LowerDict, self).pop(_ci_str(k))
        return super(LowerDict, self).pop(_ci_str(k), v)
    def update(self, mapping=(), **kwargs):
        super(LowerDict, self).update(self._process_args(mapping, **kwargs))
    def __contains__(self, k):
        return super(LowerDict, self).__contains__(_ci_str(k))
    @classmethod
    def fromkeys(cls, keys, v=None):
        return super(LowerDict, cls).fromkeys((_ci_str(k) for k in keys), v)
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(LowerDict, self).__repr__())

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

Mr_and_Mrs_D
fonte
O CIstr __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__.
Aaron Hall
Verifique também o total_orderingdecorador da classe - que eliminará 4 métodos da sua subclasse unicode. Mas a subclasse dict parece muito bem implementada. : P
Aaron Hall
Obrigado @AaronHall - foi você quem implementou isso: P Re: pedido total - escrevi intencionalmente os métodos descritos, conforme recomendado por Raymond Hettinger aqui: stackoverflow.com/a/43122305/281545 . Re: repr: Lembro-me de ler um comentário (por alguns desenvolvedores principais do IIRC), bem, não vale a pena tentar fazer com que o repr passe no teste (é um aborrecimento) - concentre-se melhor em ser o mais informativo possível ( mas não mais)
Mr_and_Mrs_D
Permitirei que você use métodos de comparação redundantes (você deve fazer uma anotação sobre isso na sua resposta), mas CIstr.__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.
Aaron Hall
@ AaronHall: eu adicionei __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)
Mr_and_Mrs_D
4

Tudo o que você precisa fazer é

class BatchCollection(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(*args, **kwargs)

OU

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

Uma amostra de uso para meu uso pessoal

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        dict.__init__(*args, **kwargs)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

Nota : testado apenas em python3

ravi404
fonte
3

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:

class MyDict(MutableMapping):
   # ... the few __methods__ that mutablemapping requires
   # and then this monstrosity
   @property
   def __class__(self):
       return dict

que eu realmente odeio, mas parece atender às minhas necessidades, que são:

  • pode substituir **my_dict
    • se você herdar dict, isso ignora seu código . Experimente.
    • isso torna o # 2 inaceitável para mim o tempo todo , pois isso é bastante comum no código python
  • disfarça como isinstance(my_dict, dict)
    • exclui o MutableMapping sozinho, então o número 1 não é suficiente
    • Eu recomendo vivamente # 1, se você não precisar disso, é simples e previsível
  • comportamento totalmente controlável
    • então eu não posso herdar de dict

Se você precisar se diferenciar dos outros, pessoalmente eu uso algo assim (embora eu recomende nomes melhores):

def __am_i_me(self):
  return True

@classmethod
def __is_it_me(cls, other):
  try:
    return other.__am_i_me()
  except Exception:
    return False

Contanto que você só precise se reconhecer internamente, é mais difícil chamar acidentalmente __am_i_medevido à troca de nomes do python (isso é renomeado para _MyDict__am_i_mequalquer coisa que chame fora desta classe). Um pouco mais privado que _methods, 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:

d = LowerDict()  # prints "init", or whatever your print statement said
print '------'
splatted = dict(**d)  # note that there are no prints here

Você verá um comportamento semelhante para outros cenários. Digamos que seu fake dictseja 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_dictficará vazio, independentemente do que qualquer outro método faça.

Isso funciona corretamente MutableMapping, mas assim que você herdar dictdela 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 para isinstanceverificações - @property __class__faz: https://repl.it/repls/UnitedScientificSequence

Groxx
fonte
O que exatamente você quer dizer com " **your_dictestará vazio" (se você subclasse de dict)? Eu não vi qualquer problema com descompactação dict ...
Matt P
Se você realmente colocar dados no dict pai (como o LowerDict), ele funcionará - você obterá esses dados armazenados no dict. Se você não o fizer (diga que deseja gerar dados dinamicamente, como {access_count: "stack trace of access"} que preenche cada vez que é lido), notará que **your_dictnã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 falhar isinstance(..., dict), então eu não poderia usá-lo. yay software legado.
Groxx
Ok, entendo o que você quer dizer agora. Suponho que não esperava execução de código **your_dict, mas acho muito interessante que isso MutableMappingseja feito.
Matt P
Sim. É necessário para várias coisas (por exemplo, eu estava analisando as chamadas de RPC para o que costumava ser uma leitura de ditado local e precisava fazer isso sob demanda pelo Reasons ™), e parece que poucas pessoas sabem disso, mesmo que **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.
Groxx
Talvez esteja faltando alguma coisa, mas o 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 quero isinstance(SpreadSheet(), dict)retornar True.
martineau