web-dev-qa-db-fra.com

La boucle infinie du dictionnaire se ferme de façon inattendue

J'expérimentais différentes façons de créer une boucle infinie dans Python (autre que l'habituel while True), et a eu cette idée:

x = {0: None}

for i in x:
    del x[i]
    x[i+1] = None  # Value doesn't matter, so I set it to None
    print(i)

Sur papier, j'ai tracé la façon dont cela se boucle indéfiniment:

  1. Je passe en revue la valeur de la clé dans le dictionnaire
  2. Je supprime cette entrée.
  3. La position actuelle du compteur dans la boucle + 1 sera la nouvelle clé avec la valeur None qui mettra à jour le dictionnaire.
  4. Je sort le compteur de courant.

Dans ma tête, cela devrait produire les nombres naturels d'une sorte de boucle infinie:

0
1
2
3
4
5
.
.
.

Je pensais que cette idée était intelligente, cependant lorsque je l'exécute sur Python 3.6, elle génère:

0
1
2
3
4

Oui, il s'est en quelque sorte arrêté après 5 itérations. De toute évidence, il n'y a pas de condition de base ou de valeur sentinelle dans le bloc de code de la boucle, alors pourquoi Python n'exécute-t-il ce code que 5 fois?

42
Suraj Kothari

Il n'y a aucune garantie que vous itérerez sur toutes vos entrées de dict si vous les mutez dans votre boucle. De la docs :

L'itération des vues lors de l'ajout ou de la suppression d'entrées dans le dictionnaire peut déclencher une erreur d'exécution ou échouer dans toutes les entrées.

Vous pouvez créer une boucle infinie "énumérée" similaire à votre tentative initiale en utilisant itertools.count() . Par exemple:

from itertools import count

for i in count():
    print(i)
    # don't run this without some mechanism to break the loop, i.e.
    # if i == 10:
    #     break

# OUTPUT
# 0
# 1
# 2
# ...and so on
44
benvc

Dans ce cas, comme l'a écrit @benvc, cela n'est pas garanti de fonctionner. Mais au cas où vous vous demandez pourquoi cela fonctionne en C-Python:

L'implémentation C-Python détruit l'objet dict après quelques insertions et le copie dans un nouvel espace en mémoire. Il ne se soucie pas des suppressions. Ainsi, lorsque cela se produit, la boucle le remarque et se rompt avec une exception.

Consultez ce lien si vous souhaitez en savoir plus à ce sujet, ainsi que de nombreux autres python internes ici).

https://github.com/satwikkansal/wtfpython#-modifying-a-dictionary- while-iterating-over-it

8
HWM-Rocker

Je viens de tester votre code en python2 et python3

python3 output
0,1,2,3,4
python2
0,1,2,3,4,5,6,7

Une chose me vient à l'esprit qui pourrait se produire. Soit il n'y a qu'une certaine quantité de mémoire allouée dans votre dictionnaire lorsque vous créez la première valeur de clé et lorsque vous supprimez la valeur de clé, nous n'allouons pas de mémoire ou désallouons la mémoire que vous supprimez simplement la valeur. Une fois que toute la mémoire allouée est utilisée, elle se ferme. Parce que si vous exécutez sans ce del, vous obtiendrez cette erreur

RuntimeError: dictionary changed size during iteration

Donc python crée suffisamment de mémoire pour cette valeur de clé et quelques autres et une fois qu'elle est utilisée, il n'y a plus de mémoire allouée pour votre dictionnaire.

5
user8128927

Comme beaucoup l'ont souligné, modifier une structure de données pendant l'itération avec une boucle for n'est pas une bonne idée. La boucle while le permet cependant car elle réévalue sa condition de boucle à chaque itération (je suis impressionné que personne ne l'ait encore suggéré comme alternative). Il suffit de trouver la bonne condition de boucle. Votre script devrait devenir:

x = {0: None}
while x:
    i, _ = x.popitem()
    print(i)
    # to avoid infinite loop while testing
    # if i == 10:
    #     break
    x[i+1] = None

En Python, un dictionnaire est faux lorsqu'il est vide (voir docs ), donc la boucle ne s'arrêtera que si au début d'une itération x est vide. Étant donné que le dictionnaire n'a qu'une seule paire clé-valeur, popitem() devrait suffire pour obtenir cette paire et la supprimer du dictionnaire. Comme le prochain entier est ajouté juste après le vidage du dictionnaire, la condition de boucle ne sera jamais fausse lorsqu'elle est évaluée, ce qui entraîne une boucle infinie.

3
J.Baoby