Outils pour utilisateurs

Outils du site


cours:informatique:fun_mooc:python3_uca_inria:260_introduction_boucles_fonctions

Python: introduction à la boucle for

Limite de la boucle for

Lorsqu'une boucle for itère sur un objet mutable, il ne faut pas modifier le sujet de la boucle.

Le code ci-dessous produira une erreur

# on souhaite enlever de l'ensemble toutes les chaînes 
# qui ne contiennent pas 'bert'
ensemble = {'marc', 'albert'}
 
# ce code ne fonctionne pas et provoque une exception
# RuntimeError: Set changed size during iteration
 
for valeur in ensemble:
    if 'bert' not in valeur:
        ensemble.discard(valeur)

Dans ce cas, une bonne solution serait de penser à une compréhension d'ensemble:

# creation d'un ensemble en compréhension
ensemble2 = {valeur for valeur in ensemble if 'bert' in valeur}

C'est sans doute la meilleure solution. Par contre, évidemment, on n'a pas modifié l'objet ensemble initial, on a créé un nouvel objet. En supposant que l'on veuille modifier l'objet initial, il nous faut faire la boucle sur une shallow copy de cet objet. Notez qu'ici, il s'agit d'économiser de la mémoire, puisque l'on fait une shallow copy.

from copy import copy
# on veut enlever de l'ensemble toutes les chaînes 
# qui ne contiennent pas 'bert'
ensemble = {'marc', 'albert'}
 
# si on fait d'abord une copie tout va bien
for valeur in copy(ensemble):
    if 'bert' not in valeur:
        ensemble.discard(valeur)
 
print(ensemble)
Dans l'exemple ci-dessus, on voit que l'interpréteur se rend compte que l'on est en train de modifier l'objet de la boucle, et nous le signifie.

Ne vous fiez pas forcément à cet exemple, il existe des cas – nous en verrons plus loin dans ce document – où l'interpréteur peut accepter votre code alors qu'il n'obéit pas à cette règle, et du coup essentiellement se mettre à faire n'importe quoi.

Pour être tout à fait clair, lorsqu'on dit qu'il ne faut pas modifier l'objet de la boucle for, il ne s'agit que du premier niveau.

On ne doit pas modifier la composition de l'objet en tant qu'itérable, mais on peut sans souci modifier chacun des objets qui constitue l'itération.

Ainsi cette construction par contre est tout à fait valide :

>>> liste = [[1], [2], [3]]
>>> print('avant', liste)
avant [[1], [2], [3]]
 
>>> for sous_liste in liste:
...    sous_liste.append(100)
 
>>> print('après', liste)
après [[1, 100], [2, 100], [3, 100]]

Dans cet exemple, les modifications ont lieu sur les éléments de liste, et non sur l'objet liste lui-même, c'est donc tout à fait légal.

Pour bien comprendre la nature de cette limitation, il faut bien voir que cela soulève deux types de problèmes distincts.

Difficulté d'ordre sémantique

D'un point de vue sémantique, si l'on voulait autoriser ce genre de choses, il faudrait définir très précisément le comportement attendu.

Considérons par exemple la situation d'une liste qui a 10 éléments, sur laquelle on ferait une boucle et que, par exemple au 5ème élément, on enlève le 8ème élément. Quel serait le comportement attendu dans ce cas ? Faut-il ou non que la boucle envisage alors le 8-ème élément ?

La situation serait encore pire pour les dictionnaires et ensembles pour lesquels l'ordre de parcours n'est pas spécifié ; ainsi on pourrait écrire du code totalement indéterministe si le parcours d'un ensemble essayait:

  • d'enlever l'élément b lorsqu'on parcourt l'élément a;
  • d'enlever l'élément a lorsqu'on parcourt l'élément b.

On le voit, il n'est déjà pas très simple d'expliciter sans ambiguïté le comportement attendu d'une boucle for qui serait autorisée à modifier son propre sujet.

Difficulté d'implémentation

Voyons maintenant un exemple de code qui ne respecte pas la règle, et qui modifie le sujet de la boucle en lui ajoutant des valeurs:

# cette boucle ne se termine pas
liste = [1, 2, 3]
for c in liste:
    if c == 3:
        liste.append(c)

Si vous essayez d'exécuter ce code sur votre ordinateur vous constaterez que la boucle ne termine pas: en fait, à chaque itération on ajoute un nouvel élément dans la liste, et du coup la boucle à un élément de plus à balayer; ce programme ne termine théoriquement jamais. En pratique, ce sera le cas quand votre système n'aura plus de mémoire disponible…

cours/informatique/fun_mooc/python3_uca_inria/260_introduction_boucles_fonctions.txt · Dernière modification : 2021/05/12 21:10 de yoann