Os dicionários são ordenados no Python 3.6+?


469

Os dicionários são ordenados no Python 3.6 (pelo menos na implementação do CPython), diferente das encarnações anteriores. Parece uma mudança substancial, mas é apenas um pequeno parágrafo na documentação . Ele é descrito como um detalhe de implementação do CPython, e não como um recurso de linguagem, mas também implica que isso pode se tornar padrão no futuro.

Como a implementação do novo dicionário funciona melhor que a anterior, preservando a ordem dos elementos?

Aqui está o texto da documentação:

dict()agora usa uma representação "compacta" pioneira em PyPy . O uso de memória do novo dict () é entre 20% e 25% menor comparado ao Python 3.5. O PEP 468 (preservando a ordem de ** kwargs em uma função.) É implementado por isso. O aspecto de preservação de pedidos dessa nova implementação é considerado um detalhe da implementação e não deve ser considerado (isso pode mudar no futuro, mas é desejável que essa nova implementação de ditado no idioma seja liberada algumas vezes antes de alterar as especificações do idioma. para exigir a semântica de preservação de pedidos para todas as implementações atuais e futuras do Python, isso também ajuda a preservar a compatibilidade com versões anteriores da linguagem em que a ordem de iteração aleatória ainda está em vigor, por exemplo, Python 3.5). (Contribuição de INADA Naoki emedição 27350 . Idéia originalmente sugerida por Raymond Hettinger .)

Atualização em dezembro de 2017: dicta retenção de pedidos de inserção é garantida para Python 3.7


2
Veja este tópico na lista de discussão do Python-Dev: mail.python.org/pipermail/python-dev/2016-September/146327.html, se você não o viu; é basicamente uma discussão sobre esses assuntos.
mgc 11/10

1
Se os kwargs agora devem ser pedidos (o que é uma boa ideia) e os kwargs são dict, não OrderedDict, acho que alguém poderia supor que as chaves dict permanecerão ordenadas na versão futura do Python, apesar da documentação dizer o contrário.
Dmitriy Sintsov

4
@DmitriySintsov Não, não faça essa suposição. Esse foi um problema levantado durante a redação do PEP, que define o recurso de preservação de pedidos **kwargse, **kwargsportanto , o texto usado é diplomático: em uma função, a assinatura agora é garantida como um mapeamento de preservação de pedidos de inserção . Eles usaram o termo mapeamento para não forçar outras implementações a ordenar o ditado (e usar um OrderedDictinternamente) e como uma maneira de sinalizar que isso não deve depender do fato de que dictnão foi ordenado.
Dimitris Fasarakis Hilliard 4/17/17

7
Uma boa explicação de vídeo a partir de Raymond Hettinger
Alex

1
@wazoox, a ordem e a complexidade do hashmap não foram alteradas. A alteração torna o hashmap menor, desperdiçando menos espaço, e o espaço economizado é (geralmente?) Mais do que a matriz auxiliar ocupa. Mais rápido, menor, ordenou - você começa a pegar todos os 3.
John La Rooy

Respostas:


512

Os dicionários são ordenados no Python 3.6+?

Eles são ordenados por inserção [1] . A partir do Python 3.6, para a implementação do Python no CPython, os dicionários lembram a ordem dos itens inseridos . Isso é considerado um detalhe de implementação no Python 3.6 ; você precisa usá- OrderedDictlo se quiser ordenar por inserção garantida em outras implementações do Python (e outro comportamento ordenado [1] ).

A partir do Python 3.7 , isso não é mais um detalhe de implementação e, em vez disso, se torna um recurso de linguagem. De uma mensagem python-dev da GvR :

Faça assim. "Dict mantém ordem de inserção" é a decisão. Obrigado!

Isso significa simplesmente que você pode depender disso . Outras implementações do Python também devem oferecer um dicionário ordenado por inserção, se desejarem ser uma implementação em conformidade do Python 3.7.


Como a 3.6implementação do dicionário Python funciona melhor [2] que a anterior, preservando a ordem dos elementos?

Essencialmente, mantendo duas matrizes .

  • A primeira matriz,, dk_entriescontém as entradas ( do tipoPyDictKeyEntry ) para o dicionário na ordem em que foram inseridas. A ordem de preservação é alcançada por ser uma matriz apenas de acréscimo em que novos itens são sempre inseridos no final (ordem de inserção).

  • O segundo, dk_indicescontém os índices para a dk_entriesmatriz (ou seja, valores que indicam a posição da entrada correspondente em dk_entries). Essa matriz atua como a tabela de hash. Quando uma chave é hash, ela leva a um dos índices armazenados dk_indicese a entrada correspondente é buscada pela indexação dk_entries. Como apenas os índices são mantidos, o tipo dessa matriz depende do tamanho geral do dicionário (variando de tipo int8_t( 1byte) a int32_t/ int64_t( 4/ 8bytes) em compilações 32/ 64bit)

Na implementação anterior, uma matriz esparsa de tipo PyDictKeyEntrye tamanho dk_sizeprecisava ser alocada; infelizmente, isso também resultou em muito espaço vazio, uma vez que não foi permitido que essa matriz estivesse mais do que 2/3 * dk_sizecheia por motivos de desempenho . (e o espaço vazio ainda tinha PyDictKeyEntrytamanho!).

Não é o caso agora, pois apenas as entradas necessárias são armazenadas (aquelas que foram inseridas) e uma matriz esparsa do tipo intX_t( Xdependendo do tamanho do ditado) 2/3 * dk_sizeé mantida cheia. O espaço vazio foi alterado de tipo PyDictKeyEntrypara intX_t.

Portanto, obviamente, criar uma matriz esparsa do tipo PyDictKeyEntryexige muito mais memória do que uma matriz esparsa para armazenar ints.

Você pode ver a conversa completa no Python-Dev sobre esse recurso, se estiver interessado, é uma boa leitura.


Na proposta original feita por Raymond Hettinger , pode-se ver uma visualização das estruturas de dados utilizadas, que captura a essência da ideia.

Por exemplo, o dicionário:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

está atualmente armazenado como [keyhash, key, value]:

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

Em vez disso, os dados devem ser organizados da seguinte maneira:

indices =  [None, 1, None, None, None, 0, None, 2]
entries =  [[-9092791511155847987, 'timmy', 'red'],
            [-8522787127447073495, 'barry', 'green'],
            [-6480567542315338377, 'guido', 'blue']]

Como você pode ver visualmente agora, na proposta original, muito espaço está essencialmente vazio para reduzir colisões e tornar as pesquisas mais rápidas. Com a nova abordagem, você reduz a memória necessária movendo a dispersão onde realmente é necessária nos índices.


[1]: Eu digo "inserção ordenada" e não "ordenada", pois, com a existência de OrderedDict, "ordenada" sugere um comportamento adicional que o dictobjeto não fornece . OrderedDicts são reversíveis, fornecem métodos sensíveis à ordem e, principalmente, fornecem testes de igualdade sensíveis à ordem ( ==, !=). dicts atualmente não oferecem nenhum desses comportamentos / métodos.


[2]: As novas implementações de dicionário têm melhor desempenho em termos de memória ao serem projetadas de forma mais compacta; esse é o principal benefício aqui. Em termos de velocidade, a diferença não é tão drástica, há lugares em que o novo ditado pode introduzir pequenas regressões ( pesquisas de teclas, por exemplo ), enquanto em outros (iteração e redimensionamento vêm à mente) um aumento de desempenho deve estar presente.

No geral, o desempenho do dicionário, especialmente em situações da vida real, melhora devido à compacidade introduzida.


15
Então, o que acontece quando um item é removido? a entrieslista é redimensionada? ou é mantido um espaço em branco? ou é comprimido de tempos em tempos?
Njzk2 11/11

18
@ njzk2 Quando um item é removido, o índice correspondente é substituído por DKIX_DUMMYum valor de -2e a entrada na entrymatriz é substituída porNULL , quando a inserção é realizada, os novos valores são anexados à matriz de entradas. Ainda não foi possível discernir, mas, com certeza, quando os índices 2/3estiverem acima do limite, o redimensionamento será realizado. Isso pode levar a um encolhimento, em vez de um aumento, se DUMMYexistirem muitas entradas.
Dimitris Fasarakis Hilliard 11/10

3
@ Chris_Rands Não, a única regressão real que eu já vi é no rastreador em uma mensagem de Victor . Além dessa marca de microbench, não vi nenhum outro problema / mensagem indicando uma séria diferença de velocidade nas cargas de trabalho da vida real. Há lugares em que o novo ditado pode introduzir pequenas regressões (pesquisas de teclas, por exemplo), enquanto em outros (iteração e redimensionamento vêm à mente) um aumento de desempenho estaria presente.
Dimitris Fasarakis Hilliard

3
Correção na parte de redimensionamento : os dicionários não são redimensionados quando você exclui itens, eles recalculam quando você insere novamente. Portanto, se um ditado for criado com d = {i:i for i in range(100)}você e .poptodos os itens sem inserção, o tamanho não será alterado. Quando você adiciona novamente, d[1] = 1o tamanho apropriado é calculado e o ditado é redimensionado.
Dimitris Fasarakis Hilliard 2/17/17

6
@Chris_Rands Tenho certeza que está ficando. O problema é que, e a razão pela qual mudei minha resposta para remover afirmações gerais sobre ' dictser ordenado', dictnão são ordenados no sentido em que OrderedDictsão. A questão notável é a igualdade. dicts têm ordem insensível ==, OrderedDicts têm ordem sensível. Despejar se OrderedDictmudar dictspara agora ter comparações sensíveis à ordem pode levar a muitas falhas no código antigo. Eu estou supondo que a única coisa que pode mudar sobre OrderedDicts é sua implementação.
Dimitris Fasarakis Hilliard

67

Abaixo está respondendo a primeira pergunta original:

Devo usar dictou OrderedDictno Python 3.6?

Eu acho que essa frase da documentação é suficiente para responder sua pergunta

O aspecto de preservação de pedidos dessa nova implementação é considerado um detalhe da implementação e não deve ser considerado

dictnão é explicitamente destinado a ser uma coleção ordenada; portanto, se você deseja manter a consistência e não confiar no efeito colateral da nova implementação, deve manter-se OrderedDict.

Faça seu código à prova do futuro :)

Há um debate sobre isso aqui .

EDIT: Python 3.7 manterá isso como um recurso, consulte


1
Parece que se eles não quiseram que fosse um recurso real, mas apenas um detalhe de implementação, eles não deveriam colocá-lo na documentação.
Xji #

3
Não tenho certeza sobre sua advertência de edição; uma vez que a garantia se aplica somente para Python 3.7, eu assumo o conselho para Python 3.6 é inalterada, ou seja dicts são ordenados em CPython, mas não conte com isso
Chris_Rands

25

Atualização: Guido van Rossum anunciou na lista de discussão que, a partir do Python 3.7 dicts em todas as implementações do Python, deve preservar a ordem de inserção.


2
Agora que a chave de pedidos é o padrão oficial, qual é o objetivo do OrderedDict? Ou agora é redundante?
Jonny Waffles

2
Eu acho que OrderedDict não será redundante porque ele tem o move_to_endmétodo e sua igualdade é sensível à ordem: docs.python.org/3/library/… . Veja a nota na resposta de Jim Fasarakis Hilliard.
fjsj

@JonnyWaffles ver a resposta de Jim e este Q & A stackoverflow.com/questions/50872498/...
Chris_Rands

3
Se você quer que seu código seja executado o mesmo em 2.7 e 3.6 / 3.7 +, você precisa usar OrderedDict
boatcoder

3
Provavelmente haverá um "UnorderedDict" em breve para pessoas que gostam de preocupar seus dicts por razões de segurança; p
ZF007

9

Eu queria acrescentar à discussão acima, mas não tenho reputação de comentar.

O Python 3.8 ainda não foi lançado, mas incluirá a reversed()função nos dicionários (removendo outra diferença de OrderedDict.

Dict e dictviews agora são iteráveis ​​na ordem de inserção reversa usando reversed (). (Contribuição de Rémi Lapeyre no bpo-33462.) Veja o que há de novo no python 3.8

Eu não vejo nenhuma menção ao operador de igualdade ou outros recursos, OrderedDictportanto eles ainda não são totalmente iguais.

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.