Que sont les objets de vue dictionnaire?

159

En python 2.7, nous avons les méthodes d'affichage du dictionnaire disponibles.

Maintenant, je connais les avantages et les inconvénients de ce qui suit:

  • dict.items()(et values, keys): renvoie une liste, vous pouvez donc stocker le résultat, et
  • dict.iteritems() (et autres): renvoie un générateur, vous pouvez donc parcourir chaque valeur générée une par une.

À quoi servent dict.viewitems()(et autres)? Quels sont leurs avantages? Comment ça marche? Qu'est-ce qu'une vue après tout?

J'ai lu que la vue reflète toujours les changements du dictionnaire. Mais comment se comporte-t-il du point de vue de la performance et de la mémoire? Quels sont les avantages et les inconvénients?

e-satis
la source

Réponses:

157

Les vues de dictionnaire sont essentiellement ce que leur nom dit: les vues sont simplement comme une fenêtre sur les clés et les valeurs (ou éléments) d'un dictionnaire. Voici un extrait de la documentation officielle 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])

(L'équivalent Python 2 utilise dishes.viewkeys()et dishes.viewvalues().)

Cet exemple montre le caractère dynamique des vues : la vue des clés n'est pas une copie des clés à un moment donné, mais plutôt une simple fenêtre qui vous montre les clés; s'ils sont modifiés, ce que vous voyez à travers la fenêtre change également. Cette fonctionnalité peut être utile dans certaines circonstances (par exemple, on peut travailler avec une vue sur les clés dans plusieurs parties d'un programme au lieu de recalculer la liste actuelle des clés à chaque fois qu'elles sont nécessaires) - notez que si les clés du dictionnaire sont modifiées lors de l'itération sur la vue, le comportement de l'itérateur n'est pas bien défini, ce qui peut entraîner des erreurs .

Un avantage est que regarder , par exemple, les clés n'utilise qu'une petite quantité de mémoire fixe et nécessite une quantité de temps processeur faible et fixe , car il n'y a pas de création de liste de clés (Python 2, d'autre part, crée souvent inutilement une nouvelle liste, comme cité par Rajendran T, qui prend de la mémoire et du temps dans une quantité proportionnelle à la longueur de la liste). Pour continuer l'analogie de la fenêtre, si vous voulez voir un paysage derrière un mur, vous faites simplement une ouverture dedans (vous construisez une fenêtre); copier les clés dans une liste correspondrait à la peinture d'une copie du paysage sur votre mur - la copie prend du temps, de l'espace et ne se met pas à jour.

Pour résumer, les vues sont simplement… des vues (fenêtres) de votre dictionnaire, qui affichent le contenu du dictionnaire même après sa modification. Ils offrent des fonctionnalités qui diffèrent de celles des listes: une liste de clés contient une copie des clés du dictionnaire à un moment donné, tandis qu'une vue est dynamique et est beaucoup plus rapide à obtenir, car elle ne doit copier aucune donnée ( clés ou valeurs) afin d'être créé.

Eric O Lebigot
la source
6
+1. Ok, en quoi cela diffère-t-il d'avoir accès directement à la liste interne des clés? Est-ce plus rapide, plus lent? Plus efficace en mémoire? Limité ? Si vous pouvez le lire et le modifier, c'est exactement la même chose que d'avoir une référence à cette liste.
e-satis le
3
Merci. Le fait est que les vues sont votre accès à "la liste interne des clés" (notez que cette "liste de clés" n'est pas une liste Python, mais est précisément une vue). Les vues sont plus efficaces en mémoire que les listes de clés (ou valeurs ou éléments) de Python 2, puisqu'elles ne copient rien; ils sont en effet comme "une référence à la liste des clés" (notez aussi que "une référence à une liste" est en fait simplement appelée une liste, en Python, car les listes sont des objets mutables). Notez également que vous ne pouvez pas modifier directement les vues: à la place, vous modifiez toujours le dictionnaire et les vues reflètent immédiatement vos modifications.
Eric O Lebigot
3
Ok, je ne suis pas encore clair sur l'implémentation, mais c'est la meilleure réponse à ce jour.
e-satis le
2
Merci. En effet, cette réponse concerne principalement la sémantique des vues. Je n'ai pas d'informations sur leur implémentation dans CPython, mais je suppose qu'une vue est essentiellement un pointeur vers la ou les bonnes structures (clés et / ou valeurs), et que les structures font partie de l'objet dictionnaire lui-même.
Eric O Lebigot
5
Je pense qu'il vaut la peine de souligner que l'exemple de code dans cet article provient de python3 et n'est pas ce que j'obtiens en python2.7.
snth
21

Comme vous l'avez mentionné, dict.items()retourne une copie de la liste des paires (clé, valeur) du dictionnaire, ce qui est inutile et dict.iteritems()renvoie un itérateur sur les paires (clé, valeur) du dictionnaire.

Prenons maintenant l'exemple suivant pour voir la différence entre un interator de dict et une vue 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

Alors qu'une vue vous montre simplement ce qu'il y a dans le dict. Peu importe si cela a changé:

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

Une vue correspond simplement à ce à quoi ressemble le dictionnaire maintenant. Après la suppression, une entrée .items()aurait été obsolète et .iteritems()aurait généré une erreur.

Martin Konecny
la source
Excellent exemple, merci. Cependant, devrait être v = d.items () pas v - d.viewitems ()
rix
1
La question concerne Python 2.7, elle viewitems()est donc correcte ( items()donne correctement une vue en Python 3 ).
Eric O Lebigot
Cependant, une vue ne peut pas être utilisée pour parcourir un dictionnaire lors de sa modification.
Ioannis Filippidis
18

En lisant les documents, j'ai cette impression:

  1. Les vues sont de type "pseudo-ensemble", en ce sens qu'elles ne prennent pas en charge l'indexation, vous pouvez donc les utiliser pour tester l'appartenance et les parcourir (parce que les clés sont hachables et uniques, les vues des clés et des éléments le sont davantage " set-like "en ce sens qu'ils ne contiennent pas de doublons).
  2. Vous pouvez les stocker et les utiliser plusieurs fois, comme les versions de liste.
  3. Parce qu'ils reflètent le dictionnaire sous-jacent, toute modification dans le dictionnaire changera la vue et changera presque certainement l'ordre d'itération . Donc, contrairement aux versions de liste, elles ne sont pas "stables".
  4. Parce qu'ils reflètent le dictionnaire sous-jacent, il s'agit presque certainement de petits objets proxy; copier les clés / valeurs / éléments exigerait qu'ils regardent d'une manière ou d'une autre le dictionnaire original et le copient plusieurs fois lorsque des changements se produisent, ce qui serait une implémentation absurde. Je m'attendrais donc à très peu de surcharge de mémoire, mais l'accès à être un peu plus lent que directement au dictionnaire.

Donc, je suppose que le cas d'utilisation clé est si vous gardez un dictionnaire et répétez à plusieurs reprises ses clés / éléments / valeurs avec des modifications entre les deux. Vous pouvez simplement utiliser une vue à la place, en vous transformant for k, v in mydict.iteritems():en for k, v in myview:. Mais si vous ne faites qu'une itération sur le dictionnaire une fois, je pense que les versions itératives sont toujours préférables.

Ben
la source
2
+1 pour analyser les avantages et les inconvénients des quelques informations que nous avons obtenues.
e-satis le
Si je crée un itérateur sur une vue, il est toujours invalidé chaque fois que le dictionnaire change. C'est le même problème qu'avec un itérateur sur le dictionnaire lui-même (par exemple iteritems()). Alors, quel est le point de ces vues? Quand suis-je content de les avoir?
Alfe
@Alfe Vous avez raison, c'est un problème avec l'itération du dictionnaire et les vues ne vous aident pas du tout. Supposons que vous deviez transmettre les valeurs d'un dictionnaire à une fonction. Vous pouvez l'utiliser .values(), mais cela implique d'en faire une copie complète sous forme de liste, ce qui pourrait coûter cher. Il y en a .itervalues()mais vous ne pouvez pas les consommer plus d'une fois, donc cela ne fonctionnera pas avec toutes les fonctions. Les vues ne nécessitent pas de copie coûteuse, mais elles sont toujours plus utiles en tant que valeur autonome qu'un itérateur. Mais ils ne sont toujours pas destinés à aider à itérer et à modifier en même temps (là, vous voulez vraiment une copie).
Ben le
17

Les méthodes d'affichage renvoient une liste (non une copie de la liste, par rapport à .keys(), .items()et .values()), il est donc plus léger, mais reflète le contenu actuel du dictionnaire.

Depuis Python 3.0 - les méthodes dict renvoient des vues - pourquoi?

La raison principale est que pour de nombreux cas d'utilisation, le retour d'une liste complètement détachée est inutile et inutile. Cela nécessiterait de copier tout le contenu (qui peut ou pas être beaucoup).

Si vous souhaitez simplement parcourir les clés, la création d'une nouvelle liste n'est pas nécessaire. Et si vous en avez vraiment besoin sous forme de liste séparée (sous forme de copie), vous pouvez facilement créer cette liste à partir de la vue.

Rajendran T
la source
6
Les méthodes de vue renvoient des objets de vue, qui ne sont pas conformes à l'interface de liste.
Matthew Trevor
5

Les vues vous permettent d'accéder à la structure de données sous-jacente, sans la copier. En plus d'être dynamique par opposition à la création d'une liste, l'une de leurs utilisations les plus utiles est le intest. Supposons que vous souhaitiez vérifier si une valeur est dans le dict ou non (que ce soit une clé ou une valeur).

La première option est de créer une liste des clés en utilisant dict.keys(), cela fonctionne mais consomme évidemment plus de mémoire. Si le dict est très grand? Ce serait un gaspillage.

Avec viewsvous pouvez itérer la structure de données réelle, sans liste intermédiaire.

Prenons des exemples. J'ai un dict avec 1000 clés de chaînes et de chiffres aléatoires et kc'est la clé que je veux rechercher

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?

Comme vous pouvez le voir, l'itération d'un viewobjet donne un énorme coup de pouce aux performances, réduisant en même temps la charge mémoire. Vous devez les utiliser lorsque vous devez effectuer des Setopérations similaires.

Remarque : j'utilise Python 2.7

Chen A.
la source
En python> = 3, je crois que .keys()renvoie une vue par défaut. Pourrait vouloir vérifier tho
Yolo Voe
1
Vous avez raison. Python 3+ fait un usage intensif des objets de vue au lieu de listes, il est beaucoup plus efficace en mémoire
Chen A.
1
Ces résultats de synchronisation sont très révélateurs, mais vérifier si l' kune des clés du dictionnaire large_dest censée être faite avec k in large_d, en Python, ce qui est probablement essentiellement aussi rapide que l'utilisation d'une vue (en d'autres termes, k in large_d.keys()n'est pas pythonique et doit être évité - tel quel k in large_d.viewkeys()).
Eric O Lebigot
Merci d'avoir fourni un exemple solide et utile. k in large_dest en fait beaucoup plus rapide que k in large_d.viewkeys(), donc cela devrait probablement être évité, mais cela a du sens pour k in large_d.viewvalues().
rien101 du