Por que os dicionários python não são reversíveis para python3.7?


11

A partir da versão 3.7, os dicionários python padrão são garantidos para manter a ordem de inserção. (*)

d = {'b': 1, 'a': 2}
for k in d: 
    print(k)
# Prints always 'b' before 'a'.

Em outras palavras, as chaves de ditado são mantidas em uma ordem estrita. Em princípio, isso permitiria que as chaves fossem reversíveis. No entanto, nenhum dos seguintes trabalhos:

# TypeError: 'dict' object is not reversible
for k in reversed(d): 
    print(k)

# TypeError: 'dict_keys' object is not reversible
for k in reversed(d.keys()): 
    print(k)

Perguntas: Qual é o raciocínio por trás desse comportamento? Por que os ditados não se tornaram reversíveis? Existem planos para mudar esse comportamento no futuro?

A solução alternativa do curso funciona:

for k in reversed(list(d.keys())): 
    print(k)

(*) De fato, esse já é o caso de instalações típicas do python 3.6, conforme discutido neste post .


Atualização : A partir do python 3.8 dicts é realmente reversível. A resposta aceita refere-se à discussão entre Guido e outros desenvolvedores principais que levaram a essa decisão. Em poucas palavras, eles ponderaram a consistência da linguagem em relação aos esforços de implementação e benefícios reais para os usuários.

Respostas:


5

Dos documentos :

invertido ( seq )

Retorne um reverso iterator. seq deve ser um objeto que possui um __reversed__()método ou suporta o protocolo de sequência (o __len__()método e o __getitem__()método com argumentos inteiros iniciando em 0).

Um dictobjeto não implementa __reversed__. Ele implementa os dois últimos métodos. Contudo,__getitem__ aceita chaves como argumentos, em vez de números inteiros (começando em 0).

Quanto ao motivo, isso já foi sugerido e discutido aqui .

EDITAR:

Essas citações são da lista de discussão Python-Dev (segmento "Adicionar métodos __reversed__ para dict", iniciado em 25. 05. 18), começarei com os argumentos "conceituais", o primeiro é de Antoine Pitrou:

Não vale nada que OrderedDict já suporte reversed (). O argumento poderia ser de duas maneiras:

  1. O dict é semelhante ao OrderedDict hoje em dia, portanto, ele também deve suportar reversed ();

  2. você pode usar o OrderedDict para sinalizar explicitamente que se importa com o pedido; não há necessidade de adicionar nada para ditar.

Meu pensamento é que a ordem de inserção garantida para ditados regulares é nova em folha, por isso levará um tempo para que a noção se estabeleça e se torne parte do pensamento diário sobre ditados. Quando isso acontece, é provavelmente inevitável que casos de uso surjam e que __reversed__ sejam adicionados em algum momento. A implementação parece direta e não é um salto conceitual esperar que uma coleção ordenada finita seja reversível.

Seguido pela resposta de Raymond Hettinger:

Dado que os ditados agora controlam a ordem de inserção, parece razoável querer saber as inserções mais recentes (ou seja, repetir as tarefas adicionadas mais recentemente em um ditado de tarefa). Outros possíveis casos de uso provavelmente corresponderão à forma como usamos o comando tail do Unix.

Se esses casos de uso surgirem, seria bom que __reversed__ já fosse suportado, para que as pessoas não fiquem tentadas a implementar uma solução alternativa feia usando chamadas popitem () seguidas de reinserções.

A principal preocupação expressa na lista de discussão é que isso adicionaria muito inchaço ou reduziria a eficiência da memória (tendo que ter listas duplamente vinculadas em vez de únicas) em pelo menos algumas implementações. Aqui está a citação de Inada Naoki do rastreador de erros do Python ( edição 33462 ):

"Ter um pedido" não significa "reversível". Por exemplo, uma lista vinculada única é ordenada, mas não reversível.

Embora a implementação do CPython possa oferecer eficiência __reverse__, adicionar __reverse__significa que toda a implementação do Python deve fornecê-la. Por exemplo, algumas implementações do Python podem ser capazes de implementar o dict com hashmap + lista vinculada única. Se __reverse__for adicionado, não será mais possível.

De volta à lista de discussão, aqui estão as duas últimas mensagens (ambas postadas em 06.08.2018). Primeiro é de Michael Selik:

Estou correto ao dizer que o consenso é +1 para inclusão na v3.8?

O último ponto do tópico foi o INADA Naoki pesquisando várias implementações e decidindo que não há problema em incluir esse recurso no 3.8. Pelo que entendi, Guido estava de acordo com os conselhos do INADA em aguardar a implementação da versão 3.7 da MicroPython. Desde que o INADA mudou de idéia, acho que é tudo a favor?

Concluindo com a mensagem de Guido van Rossum:

Isso parece certo para mim. Teremos então duas versões em que este foi o caso:

  • 3.6 onde a preservação da ordem foi implementada no CPython, mas na especificação da linguagem

  • 3.7, onde também foi adicionado à especificação do idioma

Conforme observado nas outras respostas e comentários, reversed()é suportado para dictviews e dictviews desde a versão 3.8 (14.10.2018).


5
Sua segunda citação parece uma seleção tendenciosa desse segmento. O consenso parece ser que a funcionalidade será adicionada em 3.8. Também antes de python 3.7 simplesmente não havia ordenação em um normal de dictobjeto (pelo menos não garantida da língua) para que reversedtambém não fazia sentido
FlyingTeller

Eu não tenho um cachorro na luta, acabei de citar sua primeira resposta. Mas você está certo, ponto bem entendido - eu removi a citação.
Gt #

11
Obrigado. O tópico de discussão do python-dev foi revelador. De fato, o recurso já foi implementado no python 3.8, lançado apenas dois dias atrás (14 de outubro de 2019).
Normanius


A citação de documentos não é útil, apenas sugere a próxima pergunta "então, por que o tipo de ditado não é implementado __reversed__"? O link python-dev possui conteúdo útil, mas as partes relevantes devem ser reproduzidas diretamente na resposta (pois esses links externos tendem a apodrecer).
Wim


1

Atualização para python 3.8

Dict e dictviews agora são iteráveis ​​na ordem de inserção reversa usando reversed ()

>>> dict = {1: "1", 2: "2", 3: "3"}
>>> reversed(dict)
<dict_reversekeyiterator object at 0x7f72ca795130>

Essa resposta contribui com algo novo que ainda não esteja coberto por outras respostas, comentários ou pela própria pergunta?
Normanius

@normanius Possui um exemplo de código visual lil, pode ser útil para o navegador rápido
jamylak 16/03
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.