Les générateurs sont très simples à utiliser et très puissants. Ils vous permettront d'optimiser votre code à moindre frais. Alors pourquoi se priver ?
Imaginons que je veuille extraire d'une liste de mots la liste des mots comportants le caractère 'a'. Je vais écrire une fonction.
def with_a(words):
"""
Reçoit une liste de mots et renvoie la liste des mots contenant le car. 'a'
"""
res = []
for word in words:
if 'a' in word:
res.append(word)
return res
mots = ["le", "petit", "chat", "est", "mort", "ce", "matin"]
mots_a = with_a(mots)
print("\n".join(mots_a))
chat matin
Jusque là rien de méchant. Comme il est question d'optimisation je vais mesurer le temps de traitement avec timeit
.
ipython est plein de magie, %time
hup hup hup barbatruc et voilà.
%time mots_a = with_a(mots)
mots_big = mots * 1000000
%time mots_a = with_a(mots_big)
Comme on pouvait s'y attendre le temps d'exécution de la fonction augmente avec la taille de la liste initiale.
Voyons ce que ça donne avec un générateur. Construire un générateur c'est simple : vous remplacez return
par yield
dans votre fonction.
C'est tout ? C'est tout.
Vous pouvez quand même en apprendre plus ici ou lire la PEP 255 si vous aimez ça.
def gen_with_a(words):
"""
Reçoit une liste de mots et renvoie les mots contenant le car. 'a' sous forme de générateur
"""
for word in words:
if 'a' in word:
yield(word)
mots_big = mots * 100
%time mots_a = with_a(mots_big)
%time mots_a_gen = gen_with_a(mots_big)
CPU times: user 0 ns, sys: 0 ns, total: 0 ns Wall time: 76.8 µs CPU times: user 0 ns, sys: 0 ns, total: 0 ns Wall time: 8.82 µs
😲 !!!!!!!!!
Oui c'est de la magie. Enfin c'est plutôt de la triche, regardez :
print("mots_a is a {}".format(type(mots_a)))
print("mots_a_gen is a {}".format(type(mots_a_gen)))
import sys
print("Taille de mots_a : {}".format(sys.getsizeof(mots_a)))
print("Taille de mots_a_gen : {}".format(sys.getsizeof(mots_a_gen)))
mots_a is a <class 'list'> mots_a_gen is a <class 'generator'> Taille de mots_a : 1672 Taille de mots_a_gen : 88
mots_a_gen
n'est pas une liste, c'est un objet generator
.
Il ne stocke rien ou presque en mémoire, on ne peut pas connaître sa taille (essayez len(mots_a_gen)
pour voir.
Mais c'est un itérable, on peut le parcourir comme une liste. Par contre on ne peut pas les "trancher", on ne peut accéder à un élément d'index i
comme pour une liste.
Encore une différence d'avec les listes : vous ne pouvez parcourir un générateur qu'une seule fois.
%time mots_a_gen = list(gen_with_a(mots_big))
CPU times: user 4 ms, sys: 0 ns, total: 4 ms Wall time: 5.68 ms
Mais même sans tricher les générateurs demeurent très efficaces. Vous aurez compris qu'il vous est désormais chaudement recommandé de les utiliser.
Vous pouvez aussi utiliser des générateurs en compréhension, à la manière des listes en compréhension :
[mot for mot in mots if 'a' in mot]
['chat', 'matin']
(mot for mot in mots if 'a' in mot)
<generator object <genexpr> at 0x7fd2a44637d8>