[n**2 for n in range(10)]
. Si on met des parenthèses à la place des
crochets, plutôt qu’une liste, on obtient une expression génératrice.
Mais attention aux pièges…
Pourquoi utiliser des expressions génératrices ?
Essentiellement, utiliser une expression génératrice permet de construire les éléments à la demande, plutôt qu’à l’avance. C’est utile si on veut économiser de la place mémoire (les éléments ne sont pas stockés tous à la fois en mémoire), et du temps (si on est intéressé uniquement par les premiers éléments… et qu’on ne le sait pas à l’avance…).
Le code suivant liste les 3 premiers carrés dont la somme des chiffres vaut 94 :
def sommechiffres(n):
return sum(int(c) for c in str(n))
l = [n ** 2 for n in range(10000000) if sommechiffres(n ** 2) == 94]
print(l[:3])
En utilisant les listes en intention, tous les éléments sont construits
au moment où la liste est évaluée et pas juste les 3 premiers. Ici, peu
importe l’économie de place (il y a peu de carrés qui satisfont la
condition… en réalité juste 3 qui soient dans la plage d’observation),
mais on fait un peu trop de calcul, puisque le range(...)
se «déroule»
complètement.
La même chose avec une expression génératrice :
def sommechiffres(n):
return sum(int(c) for c in str(n))
l = (n ** 2 for n in range(100000000) if sommechiffres(n ** 2) == 42)
for i in range(3):
print(next(l))
Cette fois les calculs sont faits à la demande, lors d’un appel à
next
. On ne fait que les calculs nécessaires pour obtenir les 3
premiers nombres convenables.
Le piège
Comme les générateurs sont évalués au plus tard, si on utilise des références dans les expressions à calculer, ces références ne sont résolues qu’au moment où on parcourt l’expression génératrice, et non au moment où on la crée.
Par exemple :
offset = 5
g = (offset + n for n in range(10))
print(list(g))
affichera sans surprise:
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
mais par contre:
offset = 5
g = (offset + n for n in range(10))
offset = 10
print(list(g))
affichera :
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Si on remplace dans ce qui précède les expressions génératrices par des listes:
offset = 5
g = [offset + n for n in range(10)]
offset = 10
print(g)
On a la même chose qu’on change ou non la valeur offset
après la
création de g
:
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
Attention, c’est bien l’expression à générer qui est retardée, et pas
celle du for
:
offset = 5
plage = 10
g = (offset + n for n in range(plage))
offset = 10
plage = 3
print(list(g))
affichera :
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
et pas [10, 11, 12]
car l’expression range(plage)
est évaluée au
moment où on crée l’expression génératrice.