33 votes

Modifier la liste et le dictionnaire pendant l'itération, pourquoi échoue-t-il sur dict?

Prenons l'exemple de code qui effectue une itération sur une liste lors de la suppression d'un élément à chaque itération:

x = list(range(5))

for i in x:
    print(i)
    x.pop()

Il permet d'imprimer 0, 1, 2. Seuls les trois premiers éléments sont imprimés depuis les deux derniers éléments de la liste ont été enlevés par les deux premières itérations.

Mais si vous essayez quelque chose de similaire sur un dict:

y = {i: i for i in range(5)}

for i in y:
    print(i)
    y.pop(i)

Il permet d'imprimer 0, puis élever RuntimeError: dictionary changed size during iteration, parce que nous sommes la suppression d'une clé dans le dictionnaire lors de l'itération sur elle.

Bien sûr, la modification d'une liste de au cours d'une itération est mauvais. Mais pourquoi est un RuntimeError pas élevé comme dans le cas du dictionnaire? Est-il une bonne raison à ce comportement?

32voto

Chris_Rands Points 15161

Je pense que la raison en est simple. lists sont commandés, dicts (avant Python 3.6/3.7) et sets ne le sont pas. Donc, la modification d'un lists que vous parcourez peut-être pas conseillé à titre de meilleure pratique, mais elle conduit à la cohérence, de la reproductibilité et de la garantie des comportements.

Vous pouvez l'utiliser, par exemple, disons que vous souhaitez diviser un list avec un même nombre d'éléments dans la moitié et inverser la 2ème semestre:

>>> lst = [0,1,2,3]
>>> lst2 = [lst.pop() for _ in lst]
>>> lst, lst2
([0, 1], [3, 2])

Bien sûr, il y a beaucoup mieux et plus intuitive des moyens pour effectuer cette opération, mais le point est qu'il fonctionne.

En revanche, le comportement de dicts et sets est totalement mise en œuvre spécifique puisque l'itération de l'ordre peut changer en fonction de hachage.

Vous obtenez un RunTimeError avec collections.OrderedDict, sans doute pour des raisons de cohérence avec l' dict comportement. Je ne pense pas que tout changement dans l' dict le comportement est de nature après Python 3.6 (où dicts sont garantis afin de maintenir l'insertion commandé), car il allait se casser la compatibilité descendante pour les pas de véritable cas d'utilisation.

Notez que collections.deque soulève également un RuntimeError dans ce cas, en dépit d'être commandés.

9voto

user2357112 Points 37737

Il n'aurait pas été possible d'ajouter une telle vérification des listes sans casser la compatibilité descendante. Pour les dicts, il n'y a pas eu de problème.

Dans l'ancien, pré-itérateurs de la conception, for boucles travaillées par l'appel de l'élément de la séquence de récupération de crochet avec l'augmentation de nombre entier indices jusqu'à ce qu'il a soulevé IndexError. (Je dirais __getitem__, mais c'était avant de type/classe d'unification, de sorte que C les types n'ont pas d' __getitem__.) len n'est même pas impliqué dans cette conception, et il n'est nulle part pour vérifier la modification.

Lorsque les itérateurs ont été introduits, le dict itérateur eu le changement de taille de vérifier à partir de la première commettre, qui a introduit les itérateurs de la langue. Dicts n'étaient pas itérable avant que, donc, il n'y a pas de compatibilité ascendante pour rupture. Listes est passée par la vieille itération protocole, cependant.

Lors de l' list.__iter__ a été introduit, il a été purement une optimisation de la vitesse, pas vocation à être un changement de comportement, et l'ajout d'une modification de vérifier aurait cassé la compatibilité descendante avec du code existant qui s'appuient sur l'ancien comportement.

2voto

AB Abhi Points 918

Dictionnaire utilise la commande d'insertion avec un niveau supplémentaire d'indirection, qui provoque le hoquet lors de l'itération, tandis que les touches sont enlevés et remis en place, ce qui modifie l'ordre et pointeurs internes du dictionnaire.

Et ce problème n'est pas résolu par l'itération d.keys() au lieu de d, car en Python 3, d.keys() renvoie une vision dynamique de l'clés dans l' dict ce qui entraîne le même problème. Au lieu de cela, parcourir list(d) que cela va produire une liste à partir des touches du dictionnaire qui ne changera pas au cours d'une itération

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X