¿Qué son los objetos de vista de diccionario?

158

En Python 2.7, obtuvimos el métodos de vista de diccionario disponibles los .

Ahora, conozco las ventajas y desventajas de lo siguiente:

  • dict.items()(y values, keys): devuelve una lista, para que pueda almacenar el resultado, y
  • dict.iteritems() (y similares): devuelve un generador, por lo que puede iterar sobre cada valor generado uno por uno.

Qué son dict.viewitems() (y similares)? ¿Cuáles son sus beneficios? ¿Como funciona? ¿Qué es una vista después de todo?

Leí que la vista siempre refleja los cambios del diccionario. Pero, ¿cómo se comporta desde el punto de vista de rendimiento y memoria? ¿Cuáles son las ventajas y desventajas?

e-satis
fuente

Respuestas:

157

Las vistas de diccionario son esencialmente lo que dice su nombre: las vistas son simplemente como una ventana en las claves y valores (o elementos) de un diccionario. Aquí hay un extracto de la documentación oficial de 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])

(Los usos equivalentes de Python 2 dishes.viewkeys()ydishes.viewvalues() .)

Este ejemplo muestra el carácter dinámico de las vistas : la vista de teclas no es una copia de las teclas en un momento dado, sino una ventana simple que le muestra las teclas; si se cambian, entonces lo que ves a través de la ventana también cambia. Esta característica puede ser útil en algunas circunstancias (por ejemplo, se puede trabajar con una vista de las teclas en varias partes de un programa en lugar de volver a calcular la lista actual de teclas cada vez que se necesitan); tenga en cuenta que si se modifican las teclas del diccionario al iterar sobre la vista, el comportamiento del iterador no está bien definido, lo que puede provocar errores .

Una ventaja es que mirar , por ejemplo, las teclas usa solo una cantidad pequeña y fija de memoria y requiere una cantidad pequeña y fija de tiempo de procesador , ya que no se crea una lista de teclas (Python 2, por otro lado, a menudo crea innecesariamente una nueva lista, como lo cita Rajendran T, que requiere memoria y tiempo en una cantidad proporcional a la longitud de la lista). Para continuar con la analogía de la ventana, si desea ver un paisaje detrás de una pared, simplemente haga una abertura en ella (construye una ventana); copiar las claves en una lista correspondería en lugar de pintar una copia del paisaje en su pared: la copia lleva tiempo, espacio y no se actualiza sola.

Para resumir, las vistas son simplemente ... vistas (ventanas) en su diccionario, que muestran el contenido del diccionario incluso después de que cambie. Ofrecen características que difieren de las de las listas: una lista de claves contiene una copia de las claves del diccionario en un momento dado, mientras que una vista es dinámica y es mucho más rápida de obtener, ya que no tiene que copiar ningún dato ( claves o valores) para ser creados.

Eric O Lebigot
fuente
66
+1. Ok, ¿en qué se diferencia eso de tener acceso directo a la lista interna de claves? ¿Eso es más rápido, más lento? ¿Más memoria eficiente? Restringido Si puede leerlo y editarlo, se siente exactamente lo mismo que tener una referencia a esta lista.
e-satis
3
Gracias. La cuestión es que las vistas son su acceso a "la lista interna de claves" (tenga en cuenta que esta "lista de claves" no es una lista de Python, pero es precisamente una vista). Las vistas son más eficientes en memoria que las listas de claves (o valores o elementos) de Python 2, ya que no copian nada; de hecho son como "una referencia a la lista de claves" (tenga en cuenta también que "una referencia a una lista" en realidad se llama simplemente una lista, en Python, ya que las listas son objetos mutables). También tenga en cuenta que no puede editar vistas directamente: en su lugar, aún edita el diccionario, y las vistas reflejan sus cambios de inmediato.
Eric O Lebigot
3
Ok, todavía no tengo clara la implementación, pero es la mejor respuesta hasta ahora.
e-satis
2
Gracias. De hecho, esta respuesta es principalmente sobre la semántica de las opiniones. No tengo información sobre su implementación en CPython, pero supongo que una vista es básicamente un puntero a las estructuras correctas (claves y / o valores), y que las estructuras son parte del objeto del diccionario.
Eric O Lebigot
55
Creo que vale la pena señalar que el código de ejemplo en esta publicación es de python3 y no es lo que obtengo en python2.7.
snth
21

Como mencionó, dict.items()devuelve una copia de la lista del diccionario de pares (clave, valor) que es un desperdicio y dict.iteritems()devuelve un iterador sobre los pares del diccionario (clave, valor).

Ahora tome el siguiente ejemplo para ver la diferencia entre un interador de dict y una vista 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

Mientras que una vista simplemente muestra lo que hay en el dict. No le importa si cambió:

>>> 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 es simplemente como se ve el diccionario ahora. Después de eliminar una entrada, no .items()estaría actualizada y .iteritems()habría arrojado un error.

Martin Konecny
fuente
Gran ejemplo, gracias. Sin embargo, debería ser v = d.items () no v - d.viewitems ()
rix
1
La pregunta es sobre Python 2.7, por lo que viewitems()es realmente correcta ( items()da una vista correcta en Python 3 ).
Eric O Lebigot
Sin embargo, una vista no se puede usar para iterar sobre un diccionario mientras se modifica.
Ioannis Filippidis
18

Solo de leer los documentos me da esta impresión:

  1. Las vistas son "pseudo-set-like", ya que no admiten la indexación, por lo que lo que puede hacer con ellas es probar la membresía e iterar sobre ellas (porque las claves son hashable y únicas, las vistas de claves y elementos son más " set-like "en que no contienen duplicados).
  2. Puede almacenarlos y usarlos varias veces, como las versiones de la lista.
  3. Debido a que reflejan el diccionario subyacente, cualquier cambio en el diccionario cambiará la vista y casi seguramente cambiará el orden de iteración . Entonces, a diferencia de las versiones de la lista, no son "estables".
  4. Debido a que reflejan el diccionario subyacente, es casi seguro que son pequeños objetos proxy; copiar las claves / valores / elementos requeriría que vean el diccionario original de alguna manera y lo copien varias veces cuando ocurran cambios, lo que sería una implementación absurda. Por lo tanto, esperaría muy poca sobrecarga de memoria, pero el acceso sería un poco más lento que directamente al diccionario.

Entonces, supongo que el caso de uso de la clave es si está manteniendo un diccionario alrededor e iterando repetidamente sobre sus claves / elementos / valores con modificaciones en el medio. En su lugar, podría usar una vista, convirtiéndose for k, v in mydict.iteritems():en for k, v in myview:. Pero si solo está iterando sobre el diccionario una vez, creo que las versiones iter siguen siendo preferibles.

Ben
fuente
2
+1 para analizar las ventajas y desventajas de la poca información que obtuvimos.
e-satis
Si creo un iterador sobre una vista, todavía se invalida cada vez que cambia el diccionario. Ese es el mismo problema que con un iterador sobre el propio diccionario (por ejemplo iteritems()). Entonces, ¿cuál es el punto de estas opiniones? ¿Cuándo estoy feliz de tenerlos?
Alfe
@Alfe Tienes razón, ese es un problema con la iteración del diccionario y las vistas no ayudan en absoluto. Digamos que necesita pasar los valores de un diccionario a una función. Podría usar .values(), pero eso implica hacer una copia completa como una lista, lo que podría ser costoso. Hay .itervalues()pero no puede consumirlos más de una vez, por lo que no funcionará con todas las funciones. Las vistas no requieren una copia costosa, pero siguen siendo más útiles como valor independiente que como iterador. Pero todavía no tienen la intención de ayudar a iterar y modificar al mismo tiempo (allí realmente quieres una copia).
Ben
17

Los métodos de visualización devuelven una lista (no una copia de la lista, en comparación con .keys(), .items()y .values()), por lo que es más liviana, pero refleja el contenido actual del diccionario.

Desde Python 3.0: los métodos dict devuelven vistas, ¿por qué?

La razón principal es que, para muchos casos de uso, devolver una lista completamente separada es innecesario y derrochador. Requeriría copiar todo el contenido (que puede o no ser mucho).

Si simplemente desea iterar sobre las teclas, no es necesario crear una nueva lista. Y si realmente lo necesita como una lista separada (como una copia), puede crear fácilmente esa lista desde la vista.

Rajendran T
fuente
66
Los métodos de vista devuelven objetos de vista, que no se ajustan a la interfaz de la lista.
Matthew Trevor
5

Las vistas le permiten acceder a la estructura de datos subyacentes, sin copiarla. Además de ser dinámico en lugar de crear una lista, uno de sus usos más útiles es la inprueba. Digamos que desea verificar si un valor está en el dict o no (ya sea clave o valor).

La opción uno es crear una lista de las teclas usando dict.keys(), esto funciona pero obviamente consume más memoria. Si el dict es muy grande? Eso sería un desperdicio.

Con viewsusted puede iterar la estructura de datos real, sin una lista intermedia.

Usemos ejemplos. Tengo un dict con 1000 claves de cadenas y dígitos aleatorios y kes la clave que quiero buscar

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 puede ver, la iteración de viewobjetos da un gran impulso al rendimiento, reduciendo la sobrecarga de memoria al mismo tiempo. Debe usarlos cuando necesite realizar Setoperaciones similares.

Nota : estoy corriendo en Python 2.7

Chen A.
fuente
En python> = 3, creo que .keys()devuelve una vista por defecto. Puede que quiera comprobarlo dos veces
Yolo Voe
1
Tienes razón. Python 3+ hace un uso intensivo de los objetos de vista en lugar de listas, es mucho más eficiente en memoria
Chen A.
1
Estos resultados de tiempo son muy reveladores, pero verificar si kuna de las claves del diccionario large_dse debe hacer k in large_d, en Python, que probablemente sea tan rápido como usar una vista (en otras palabras, k in large_d.keys()no es Pythonic y debería evitarse) como es k in large_d.viewkeys())
Eric O Lebigot
Gracias por proporcionar un ejemplo sólido y útil. k in large_den realidad es significativamente más rápido que k in large_d.viewkeys(), por lo que probablemente debería evitarse, pero esto tiene sentido k in large_d.viewvalues().
nada101