O que são objetos de exibição de dicionário?

158

No python 2.7, temos os métodos de visualização de dicionário disponíveis.

Agora, conheço os prós e contras do seguinte:

  • dict.items()(e values, keys): retorna uma lista, para que você possa realmente armazenar o resultado e
  • dict.iteritems() (e similares): retorna um gerador, para que você possa iterar sobre cada valor gerado um por um.

Para que servem dict.viewitems()(e similares)? Quais são os benefícios deles? Como funciona? Afinal, o que é uma visão?

Eu li que a visualização está sempre refletindo as alterações do dicionário. Mas como se comporta do ponto de vista do desempenho e da memória? Quais são os prós e contras?

e-satis
fonte

Respostas:

157

As visualizações de dicionário são essencialmente o que seu nome diz: as visualizações são simplesmente como uma janela nas chaves e valores (ou itens) de um dicionário. Aqui está um trecho da documentação oficial do Python 3:

>>> dishes = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500}
>>> keys = dishes.keys()
>>> values = dishes.values()

>>> # view objects are dynamic and reflect dict changes
>>> del dishes['eggs']
>>> keys  # No eggs anymore!
dict_keys(['sausage', 'bacon', 'spam'])

>>> values  # No eggs value (2) anymore!
dict_values([1, 1, 500])

(O equivalente do Python 2 usa dishes.viewkeys()e dishes.viewvalues().)

Este exemplo mostra o caráter dinâmico das visualizações : a visualização de chaves não é uma cópia das chaves em um determinado momento, mas uma janela simples que mostra as chaves; se eles forem alterados, o que você vê através da janela também muda. Esse recurso pode ser útil em algumas circunstâncias (por exemplo, é possível trabalhar com uma exibição nas chaves em várias partes de um programa em vez de recalcular a lista atual de chaves sempre que necessário) - observe que, se as chaves do dicionário forem modificadas ao iterar sobre a exibição, o comportamento do iterador não está bem definido, o que pode levar a erros .

Uma vantagem é que olhar para, digamos, as chaves usa apenas um pequeno e fixo quantidade de memória e requer um pequeno e fixo quantidade de tempo do processador , já que não há criação de uma lista de chaves (Python 2, por outro lado, geralmente cria desnecessariamente uma nova lista, conforme citado por Rajendran T, que leva memória e tempo em uma quantidade proporcional ao comprimento da lista). Para continuar a analogia da janela, se você quiser ver uma paisagem atrás de uma parede, basta fazer uma abertura nela (você constrói uma janela); copiar as chaves em uma lista corresponderia a pintar uma cópia da paisagem na parede - a cópia ocupa tempo, espaço e não se atualiza.

Para resumir, as visualizações são simplesmente ... visualizações (janelas) no seu dicionário, que mostram o conteúdo do dicionário mesmo depois que ele é alterado. Eles oferecem recursos diferentes dos das listas: uma lista de chaves contém uma cópia das chaves do dicionário em um determinado momento, enquanto uma exibição é dinâmica e é muito mais rápida de obter, pois não precisa copiar nenhum dado ( chaves ou valores) para serem criados.

Eric O Lebigot
fonte
6
+1. Ok, como isso difere de ter acesso direto à lista interna de chaves? Isso é mais rápido, mais lento? Mais memória eficiente? Restrito ? Se você pode ler e editar, parece exatamente o mesmo que ter uma referência a esta lista.
e-satis
3
Obrigado. O ponto é que as visualizações são seu acesso à "lista interna de chaves" (observe que essa "lista de chaves" não é uma lista do Python, mas é precisamente uma visualização). As visualizações são mais eficientes em termos de memória do que as listas de chaves (ou valores ou itens) do Python 2, pois não copiam nada; eles são realmente como "uma referência à lista de chaves" (observe também que "uma referência a uma lista" é na verdade simplesmente chamada de lista, em Python, pois listas são objetos mutáveis). Observe também que não é possível editar diretamente as visualizações: em vez disso, você ainda edita o dicionário e as visualizações refletem suas alterações imediatamente.
Eric O Lebigot
3
Ok, ainda não estou claro sobre a implementação, mas é a melhor resposta até agora.
e-satis
2
Obrigado. De fato, essa resposta é principalmente sobre a semântica das visualizações. Não tenho informações sobre sua implementação no CPython, mas acho que uma visão é basicamente um ponteiro para a (s) estrutura (s) correta (s) (chaves e / ou valores) e que as estruturas fazem parte do próprio objeto do dicionário.
Eric O Lebigot
5
Eu acho que vale ressaltar que o código de exemplo neste post é do python3 e não é o que eu recebo no python2.7.
snth
21

Como você mencionou, dict.items()retorna uma cópia da lista de pares (chave, valor) do dicionário, que é um desperdício e dict.iteritems()retorna um iterador sobre os pares (chave, valor) do dicionário.

Agora, pegue o exemplo a seguir para ver a diferença entre um interator de dict e uma visão de dict

>>> d = {"x":5, "y":3}
>>> iter = d.iteritems()
>>> del d["x"]
>>> for i in iter: print i
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

Considerando que uma visão simplesmente mostra o que está no ditado. Não importa se mudou:

>>> d = {"x":5, "y":3}
>>> v = d.viewitems()
>>> v
dict_items([('y', 3), ('x', 5)])
>>> del d["x"]
>>> v
dict_items([('y', 3)])

Uma visualização é simplesmente a aparência do dicionário agora. Após a exclusão, uma entrada .items()estaria desatualizada e .iteritems()geraria um erro.

Martin Konecny
fonte
Ótimo exemplo, obrigado. Embora, deva ser v = d.items () não v - d.viewitems ()
rix
1
A questão é sobre o Python 2.7, portanto, viewitems()é realmente correto ( items()fornece uma visão correta no Python 3 ).
Eric O Lebigot
No entanto, uma visualização não pode ser usada para iterar sobre um dicionário enquanto a modifica.
Ioannis Filippidis
18

Só de ler os documentos, recebo esta impressão:

  1. As visualizações são "parecidas com pseudo-conjunto", pois não oferecem suporte à indexação; portanto, o que você pode fazer com elas é testar a associação e iterá-las (porque as chaves são laváveis ​​e exclusivas, as visualizações de chaves e itens são mais " set-like ", pois eles não contêm duplicatas).
  2. Você pode armazená-los e usá-los várias vezes, como as versões da lista.
  3. Como eles refletem o dicionário subjacente, qualquer alteração no dicionário mudará a exibição e quase certamente mudará a ordem da iteração . Portanto, diferentemente das versões da lista, elas não são "estáveis".
  4. Como eles refletem o dicionário subjacente, são quase certamente pequenos objetos proxy; copiar as chaves / valores / itens exigiria que eles assistissem ao dicionário original de alguma forma e o copiassem várias vezes quando ocorrerem mudanças, o que seria uma implementação absurda. Então, eu esperaria muito pouca sobrecarga de memória, mas o acesso seria um pouco mais lento do que diretamente no dicionário.

Então, acho que o principal caso de uso é se você está mantendo um dicionário por perto e repetidamente repetindo suas chaves / itens / valores com modificações no meio. Você poderia apenas usar uma visualização, transformando for k, v in mydict.iteritems():-a for k, v in myview:. Mas se você estiver apenas repetindo o dicionário uma vez, acho que as versões iterativas ainda são preferíveis.

Ben
fonte
2
+1 por analisar os prós e contras das poucas informações que obtivemos.
e-satis
Se eu criar um iterador sobre uma exibição, ele ainda será invalidado sempre que o dicionário for alterado. Esse é o mesmo problema que com um iterador sobre o próprio dicionário (por exemplo iteritems()). Então, qual é o objetivo dessas visões? Quando fico feliz em tê-los?
Alfe
@ Alfe Você está certo, isso é um problema com a iteração do dicionário e as visualizações não ajudam em nada. Digamos que você precise passar os valores de um dicionário para uma função. Você pode usar .values(), mas isso envolve fazer uma cópia inteira como uma lista, o que pode ser caro. Há, .itervalues()mas você não pode consumi-las mais de uma vez, para que não funcione com todas as funções. As visualizações não exigem uma cópia cara, mas ainda são mais úteis como valor independente do que um iterador. Mas eles ainda não têm a intenção de ajudar na iteração e modificação ao mesmo tempo (lá você realmente deseja uma cópia).
Ben
17

Os métodos de vista retornar uma lista (não uma cópia da lista, em comparação com .keys(), .items()e .values()), por isso é mais leve, mas reflete o conteúdo atual do dicionário.

No Python 3.0 - métodos dict retornam visualizações - por que?

O principal motivo é que, para muitos casos de uso, retornar uma lista completamente desanexada é desnecessário e desperdício. Seria necessário copiar todo o conteúdo (que pode ou não ser muito).

Se você simplesmente deseja iterar sobre as chaves, não será necessário criar uma nova lista. E se você realmente precisar dela como uma lista separada (como uma cópia), poderá facilmente criar essa lista a partir da visualização.

Rajendran T
fonte
6
Os métodos de exibição retornam objetos de exibição que não estão em conformidade com a interface da lista.
Matthew Trevor
5

As visualizações permitem acessar a estrutura de dados subjacente, sem copiá-la. Além de dinâmico, em vez de criar uma lista, um dos usos mais úteis é o inteste. Digamos que você queira verificar se um valor está no ditado ou não (seja chave ou valor).

A primeira opção é criar uma lista de chaves usando dict.keys(), isso funciona, mas obviamente consome mais memória. Se o ditado é muito grande? Isso seria um desperdício.

Com viewsvocê pode iterar a estrutura de dados real, sem lista intermediária.

Vamos usar exemplos. Eu tenho um ditado com 1000 teclas de seqüências e dígitos aleatórios e ké a chave que eu quero procurar

large_d = { .. 'NBBDC': '0RMLH', 'E01AS': 'UAZIQ', 'G0SSL': '6117Y', 'LYBZ7': 'VC8JQ' .. }

>>> len(large_d)
1000

# this is one option; It creates the keys() list every time, it's here just for the example
timeit.timeit('k in large_d.keys()', setup='from __main__ import large_d, k', number=1000000)
13.748743600954867


# now let's create the list first; only then check for containment
>>> list_keys = large_d.keys()
>>> timeit.timeit('k in list_keys', setup='from __main__ import large_d, k, list_keys', number=1000000)
8.874809793833492


# this saves us ~5 seconds. Great!
# let's try the views now
>>> timeit.timeit('k in large_d.viewkeys()', setup='from __main__ import large_d, k', number=1000000)
0.08828549011070663

# How about saving another 8.5 seconds?

Como você pode ver, o viewobjeto de iteração dá um enorme impulso ao desempenho, reduzindo a sobrecarga de memória ao mesmo tempo. Você deve usá-los quando precisar executar Setoperações semelhantes.

Nota : Estou executando no Python 2.7

Chen A.
fonte
Em python> = 3, acredito que .keys()retorna uma visualização por padrão. Pode querer checar duas vezes tho
Yolo Voe
1
Você está certo. Python 3+ fazer uso pesado de vista objetos em vez de listas, é muito mais eficiente de memória
Chen A.
1
Esses resultados de tempo são muito reveladores, mas verificar se kuma das chaves do dicionário large_ddeve ser feito k in large_dno Python, que é provavelmente tão rápido quanto o uso de uma exibição (em outras palavras, k in large_d.keys()não é Pythonic e deve ser evitado - como está k in large_d.viewkeys()).
Eric O Lebigot
Obrigado por fornecer um exemplo sólido e útil. k in large_dé realmente significativamente mais rápido que k in large_d.viewkeys(), portanto, isso provavelmente deve ser evitado, mas isso faz sentido k in large_d.viewvalues().
naught101