Générateurs





Dans cette section, nous verrons successivement

  1. le fonctionnement d’un générateur
  2. la méthode __next__()
  3. l’ objet range

Le fonctionnement d’un générateur

 
Dans la section consacrée aux ensembles itérables, nous avons vu qu’une séquence, par exemple de type list, pouvait être renseignée sur une seule ligne de code, par une boucle entre crochets (comprehension list).
Ex: liste constituée des carrés des 10 premiers entiers
 

 

Si nous appliquons ce procédé à un objet de type tuple, le résultat est inattendu
 

 
A la place d’un tuplet, on obtient un objet de type generator (générateur) avec une adresse en mémoire, sans affichage des éléments calculés par compréhension. Pour visualiser le tuplet, il faut d’abord convertir l’objet en liste, puis le reconvertir en tuplet.

 

 
Au moment de le convertir en tuplet, l’objet semble avoir disparu, comme si le premier affichage de la liste avait fait disparaître les valeurs précédemment en mémoire. Cela se confirme dans le code ci-dessous, où la liste n’apparaît qu’une fois, alors que son affichage est demandé 3 fois
 

 
Conclusion: l’expression list(t) ne se contente pas de convertir un générateur en liste, elle effectue une opération qui ne peut pas être réitérée. Autrement dit, le générateur contient l’information permettant de générer une seule fois la séquence, mais il ne peut pas recommencer à moins d’être recréé. En pratique, après avoir généré une valeur, il ne garde plus trace de celle qui a précédé.
L’espace mémoire occupé par le générateur devrait normalement être faible, puisque les éléments à générer ne sont pas destinés à être sauvegardés. L’exemple suivant le confirme (la taille de la boucle a été volontairement grossie)
 

 
Donc, le générateur sert à « préparer la machine » pour produire itérativement des valeurs, en gardant juste une trace de l’itération précédente (c’est à dire le numéro d’étape). Il n’est pas possible d’engendrer une suite par récurrence avec un générateur, à moins d’utiliser la méthode __next__() présentée au paragraphe qui suit.
On notera qu’une liste prend un peu plus de place qu’un tuplet, pour un contenu identique: un objet immutable demande moins d’informations qu’un objet mutable pour y avoir accès (semble t’il).

La méthode __next__()

 
Au lieu de produire en bloc tous les éléments d’une séquence, il est possible de les faire apparaître un par un, en se donnant la possibilité d’interrompre le processus si besoin. Pour cela, on utilise la méthode __next__(). L’exécution de __next__() juste après avoir déclaré le générateur, permet d’afficher le premier élément de la séquence: un deuxième appel permet d’afficher le deuxième, etc.
On peut écrire une boucle qui lancera cette méthode autant de fois que souhaité, comme dans l’exemple suivant. On notera que chaque exécution de __next__() diminue la longueur de la séquence générée de 1.
 

 
Lorsque x**2 est supérieur ou égal à 100, c’est à dire quand x=10, le programme sort de la boucle et la séquence qui est générée par list(t) ne concerne que les éléments à partir de x=11 jusqu’à x=19: c’est pourquoi, len(list(t))=9.
Nous verrons dans la section § Fonctions qu’un générateur peut remplacer une fonction.
 

L’objet range

 
Dans la création d’un tuplet en compréhension, qui aboutit à un générateur, nous avons utilisé naturellement le mot-clé range permettant de boucler sur un nombre fixé d’itérations. Hors, dans la version 2.7, range est un objet de type list, c’est à dire que la boucle est programmée en parcourant une séquence qui est déjà intégralement présente en mémoire. D’où la question: quel est l’intérêt d’utiliser un générateur, sachant que la création de celui-ci nécessite de monopoliser autant de mémoire que la création d’une séquence (autre que tuplet) en compréhension ?
La réponse est dans le changement intervenu avec les versions 3 de Python
 

 
range, est un objet itérable à part (ni séquence, ni générateur) qui n’a pas de méthode __next__() attachée mais une méthode __iter__() appelée itérateur qui permet d’obtenir un objet de type <class ‘range_iterator’>, proche du générateur.
 

 
La méthode __iter_() s’emploie pour tout type de séquence