Cosa sono gli oggetti vista dizionario?


158

In Python 2.7, sono disponibili i metodi di visualizzazione del dizionario .

Ora conosco i pro e i contro di quanto segue:

  • dict.items()(e values, keys): restituisce un elenco, in modo da poter effettivamente archiviare il risultato e
  • dict.iteritems() (e simili): restituisce un generatore, quindi puoi iterare su ogni valore generato uno per uno.

A cosa servono dict.viewitems()(e simili)? Quali sono i loro benefici? Come funziona? Cos'è una vista dopo tutto?

Ho letto che la vista riflette sempre le modifiche dal dizionario. Ma come si comporta dal punto di vista del perf e della memoria? Quali sono i pro e contro?

Risposte:


157

Le viste del dizionario sono essenzialmente ciò che dice il loro nome: le viste sono semplicemente come una finestra sulle chiavi e sui valori (o elementi) di un dizionario. Ecco un estratto dalla documentazione ufficiale per 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])

(L'equivalente di Python 2 usa dishes.viewkeys()edishes.viewvalues() .)

Questo esempio mostra il carattere dinamico delle viste : la vista chiavi non è una copia delle chiavi in ​​un dato momento, ma piuttosto una semplice finestra che mostra le chiavi; se sono cambiati, cambia anche quello che vedi attraverso la finestra. Questa funzione può essere utile in alcune circostanze (ad esempio, si può lavorare con una vista sulle chiavi in ​​più parti di un programma invece di ricalcolare l'elenco corrente di chiavi ogni volta che sono necessarie) - notare che se le chiavi del dizionario vengono modificate durante l'iterazione della vista, il comportamento dell'iteratore non è ben definito, il che può causare errori .

Un vantaggio è che guardando , diciamo, i tasti usano solo una piccola e fissa quantità di memoria e richiedono una piccola e fissa quantità di tempo del processore , in quanto non v'è alcuna creazione di un elenco di chiavi (Python 2, d'altra parte, spesso crea inutilmente un nuovo elenco, come citato da Rajendran T, che richiede memoria e tempo in una quantità proporzionale alla lunghezza dell'elenco). Per continuare l'analogia della finestra, se vuoi vedere un paesaggio dietro un muro, fai semplicemente un'apertura (costruisci una finestra); copiare le chiavi in ​​un elenco corrisponderebbe invece a dipingere una copia del paesaggio sul muro: la copia richiede tempo, spazio e non si aggiorna da sola.

Per riassumere, le viste sono semplicemente ... viste (finestre) sul tuo dizionario, che mostrano il contenuto del dizionario anche dopo che è stato modificato. Offrono funzionalità diverse da quelle degli elenchi: un elenco di chiavi contiene una copia delle chiavi del dizionario in un determinato momento, mentre una vista è dinamica ed è molto più veloce da ottenere, poiché non è necessario copiare alcun dato ( chiavi o valori) per essere creati.


6
+1. Ok, in che modo differisce dall'accesso diretto all'elenco interno di chiavi? È più veloce, più lento? Più memoria efficiente? Limitato? Se riesci a leggerlo e modificarlo, sembra esattamente come avere un riferimento a questo elenco.
e-soddisfa il

3
Grazie. Il fatto è che le viste sono il tuo accesso all '"elenco interno di chiavi" (nota che questo "elenco di chiavi" non è un elenco Python, ma è proprio una vista). Le viste sono più efficienti in termini di memoria rispetto agli elenchi di chiavi (o valori o elementi) di Python 2, poiché non copiano nulla; sono davvero come "un riferimento all'elenco di chiavi" (nota anche che "un riferimento a un elenco" in realtà è semplicemente chiamato un elenco, in Python, poiché gli elenchi sono oggetti mutabili). Si noti inoltre che non è possibile modificare direttamente le visualizzazioni: invece, si modifica comunque il dizionario e le visualizzazioni riflettono immediatamente le modifiche.
Eric O Lebigot,

3
Ok, non sono ancora chiaro sull'implementazione, ma è la risposta migliore finora.
e-soddisfa il

2
Grazie. In effetti, questa risposta riguarda principalmente la semantica delle viste. Non ho informazioni sulla loro implementazione in CPython, ma immagino che una vista sia fondamentalmente un puntatore alle giuste strutture (chiavi e / o valori) e che le strutture facciano parte dell'oggetto dizionario stesso.
Eric O Lebigot,

5
Penso che valga la pena sottolineare che il codice di esempio in questo post proviene da python3 e non è quello che ottengo in python2.7.
dal

21

Come hai detto, dict.items()restituisce una copia dell'elenco di coppie (chiave, valore) dict.iteritems()del dizionario che è dispendioso e restituisce un iteratore sulle coppie (chiave, valore) del dizionario.

Ora prendi il seguente esempio per vedere la differenza tra un interatore di dict e una vista di 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 che una vista mostra semplicemente cosa c'è nel dict. Non importa se è cambiato:

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

Una vista è semplicemente come appare ora il dizionario. Dopo aver eliminato una voce .items()sarebbe stata superata e .iteritems()avrebbe generato un errore.


Ottimo esempio, grazie. Tuttavia, dovrebbe essere v = d.items () non v - d.viewitems ()
rix

1
La domanda riguarda Python 2.7, quindi in viewitems()realtà è corretta ( items()fornisce correttamente una vista in Python 3 ).
Eric O Lebigot,

Tuttavia, non è possibile utilizzare una vista per scorrere su un dizionario durante la modifica.
Ioannis Filippidis,

18

Solo leggendo i documenti ho avuto questa impressione:

  1. Le viste sono "pseudo-set-like", in quanto non supportano l'indicizzazione, quindi quello che puoi fare con loro è testare l'appartenenza e iterare su di esse (poiché le chiavi sono hashing e uniche, le viste chiavi ed elementi sono più " set-like "in quanto non contengono duplicati).
  2. Puoi memorizzarli e usarli più volte, come le versioni dell'elenco.
  3. Poiché riflettono il dizionario sottostante, qualsiasi modifica nel dizionario cambierà la vista e quasi sicuramente cambierà l'ordine di iterazione . Quindi, diversamente dalle versioni dell'elenco, non sono "stabili".
  4. Poiché riflettono il dizionario sottostante, sono quasi certamente piccoli oggetti proxy; copiare le chiavi / i valori / gli elementi richiederebbe che guardassero il dizionario originale in qualche modo e lo copiassero più volte quando si verificano cambiamenti, il che sarebbe una implementazione assurda. Quindi mi aspetto un minimo sovraccarico di memoria, ma l'accesso è un po 'più lento rispetto direttamente al dizionario.

Quindi immagino che la chiave di utilizzo sia se stai tenendo un dizionario in giro e ripetendo ripetutamente le sue chiavi / voci / valori con modifiche in mezzo. Invece puoi semplicemente usare una vista, trasformandola for k, v in mydict.iteritems():in for k, v in myview:. Ma se stai solo ripetendo il dizionario una volta, penso che le versioni iter siano ancora preferibili.


2
+1 per analizzare i pro e i contro dalle poche informazioni che abbiamo ottenuto.
e-soddisfa il

Se creo un iteratore su una vista, viene comunque invalidato ogni volta che il dizionario cambia. Questo è lo stesso problema di un iteratore sul dizionario stesso (ad es iteritems().). Allora, qual è il punto di vista? Quando sono felice di averli?
Alfe,

@Alfe Hai ragione, questo è un problema con l'iterazione del dizionario e le visualizzazioni non aiutano affatto. Supponi di dover passare i valori di un dizionario a una funzione. È possibile utilizzare .values(), ma ciò comporta la creazione di una copia intera come elenco, che potrebbe essere costoso. C'è .itervalues()ma non puoi consumarli più di una volta, quindi non funzionerà con ogni funzione. Le viste non richiedono una copia costosa, ma sono ancora più utili come valore autonomo di un iteratore. Ma non sono ancora destinati ad aiutare con l'iterazione e la modifica allo stesso tempo (lì vuoi davvero una copia).
Ben

17

I metodi di visualizzazione restituiscono un elenco (non una copia dell'elenco, rispetto a .keys(), .items()e .values()), quindi è più leggero, ma riflette il contenuto corrente del dizionario.

Da Python 3.0 - i metodi dict restituiscono visualizzazioni - perché?

Il motivo principale è che per molti casi d'uso la restituzione di un elenco completamente distaccato è superflua e inutile. Richiederebbe la copia dell'intero contenuto (che può essere o non essere molto).

Se desideri semplicemente scorrere le chiavi, non è necessario creare un nuovo elenco. E se davvero ne hai bisogno come elenco separato (come copia), puoi facilmente creare tale elenco dalla vista.


6
I metodi di visualizzazione restituiscono oggetti vista, che non sono conformi all'interfaccia elenco.
Matthew Trevor,

5

Le viste consentono di accedere alla struttura dei dati di base, senza copiarla. Oltre ad essere dinamico rispetto alla creazione di un elenco, uno dei loro usi più utili è il intest. Supponi di voler verificare se un valore è nel dict o meno (che sia chiave o valore).

L'opzione uno è quella di creare un elenco di chiavi usando dict.keys(), questo funziona ma ovviamente consuma più memoria. Se il dict è molto grande? Sarebbe uno spreco.

Con viewste puoi ripetere l'effettiva struttura dei dati, senza un elenco intermedio.

Usiamo esempi. Ho un dict con 1000 chiavi di stringhe e cifre casuali ed kè la chiave che voglio cercare

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?

Come puoi vedere, l'iterazione viewdell'oggetto aumenta notevolmente le prestazioni, riducendo al contempo l'overhead di memoria. Dovresti usarli quando devi eseguire Setoperazioni simili.

Nota : sto usando Python 2.7


In python> = 3, credo .keys()restituisca una vista per impostazione predefinita. Potrebbe voler ricontrollare tho
Yolo Voe il

1
Hai ragione. Python 3+ fa un uso intenso degli oggetti vista invece degli elenchi, è molto più efficiente in termini di memoria
Chen A.

1
Questi risultati di temporizzazione sono molto indicativi, ma verificare se si ktratta di una delle chiavi del dizionario large_ddeve essere fatto con k in large_d, in Python, che è probabilmente essenzialmente veloce come usare una vista (in altre parole, k in large_d.keys()non è Pythonic e dovrebbe essere evitato— così com'è k in large_d.viewkeys()).
Eric O Lebigot,

Grazie per aver fornito un esempio solido e utile. k in large_dè in realtà significativamente più veloce di k in large_d.viewkeys(), quindi probabilmente dovrebbe essere evitato, ma questo ha senso k in large_d.viewvalues().
naught101
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.