Générateurs en Python

Générateurs en Python

Cet article explique les générateurs en Python.

YouTube Video

Générateurs en Python

Aperçu

Les générateurs Python sont un type d'itérateur et une fonctionnalité puissante pour effectuer un traitement répétitif efficacement. Ils permettent d'écrire un code optimisé pour la mémoire lors du traitement de grandes quantités de données.

Qu'est-ce qu'un générateur ?

Un générateur en Python est une fonction spéciale qui produit une valeur à la fois, définie à l'aide du mot-clé yield. Sa caractéristique est qu'il suspend l'exécution tout en conservant son état et peut reprendre plus tard.

Principes de base de yield

yield est un mot-clé qui renvoie une valeur et suspend l'exécution de la fonction en même temps.

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • Lorsqu'elle est appelée, cette fonction retourne un objet générateur qui génère les valeurs une par une.
  • Si vous appelez next() alors qu'il n'y a plus de valeur suivante, une erreur StopIteration se produira.

next() et StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • En gérant explicitement l'erreur StopIteration de cette manière, vous pouvez détecter quand un générateur est terminé.

send(value)

Appeler send(value) reprend le générateur et envoie value à la position de l'expression yield. La valeur envoyée peut être reçue côté générateur comme la valeur de retour de l'expression yield. Lors du premier appel, vous ne pouvez rien envoyer d'autre que None avec send(value), donc vous devez utiliser next() ou send(None).

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • Avec send(10), le yield du générateur devient une expression qui retourne 10, et 10 est assigné à x.

throw()

Appeler throw reprend le générateur et lève une exception à la position du yield en pause. Vous pouvez gérer l'exception à l'intérieur du générateur pour poursuivre le traitement. Si l'exception n'est pas interceptée, elle se propage vers l'extérieur et le générateur se termine.

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • Dans ce code, throw est appelé pour injecter une exception dans le générateur. Côté générateur, l'exception est gérée et recovered est retourné.

close()

Appeler close() termine le générateur. À l'intérieur du générateur, vous pouvez effectuer le nettoyage avec finally. Appeler next() ou send() après avoir appelé close() lève une erreur StopIteration.

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • Ce code montre qu'appeler close() termine le générateur et déclenche le processus de nettoyage dans finally.

yield from

yield from est une syntaxe utilisée pour déléguer à un sous-générateur. C'est une façon simple d'appeler un autre générateur à l'intérieur d'un générateur et de faire passer toutes ses valeurs à l'extérieur.

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • Ce code délègue toutes les valeurs du sous-générateur au générateur externe en utilisant yield from, puis il produit 3.

Relation avec les itérateurs

Les générateurs implémentent en interne __iter__() et __next__(), ce qui en fait un type d'itérateur. Ils sont donc entièrement compatibles avec les opérations itérables telles que les boucles for.

Intégration avec les boucles for

En Python, une boucle for utilise en interne next() pour récupérer automatiquement les valeurs.

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

Avec cette méthode, la gestion de StopIteration est également automatique.

Créer des générateurs infinis

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

Il est possible de créer des boucles infinies, mais il faut être prudent lors de leur utilisation.

Expressions génératrices

Les expressions génératrices, écrites avec des parenthèses, permettent de définir des générateurs avec une syntaxe similaire aux compréhensions de listes.

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

Contrairement aux compréhensions de listes, elles ne chargent pas tous les éléments en mémoire à la fois, ce qui les rend plus économes en mémoire.

Gestion des erreurs dans les générateurs

Des exceptions peuvent se produire à l'intérieur d'un générateur. Dans ces cas-là, vous utilisez try-except comme dans un code Python standard.

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

Dans cet exemple, une gestion appropriée des erreurs est effectuée en cas de division par zéro.

Trace de la pile d'un générateur

Si une exception se produit à l'intérieur du générateur, elle sera levée lorsque le générateur reprendra.

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • Ce générateur renvoie d'abord 1. L'erreur levée lors de la reprise est capturée et affichée comme message d'erreur.

Exemples d'utilisation des générateurs

Lecture d'un fichier ligne par ligne (adaptée aux gros fichiers)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • Cette fonction lit un fichier texte ligne par ligne à l'aide d'un itérateur, enlève les espaces de chaque ligne, puis les retourne sous forme de générateur, permettant le traitement de gros fichiers avec une faible utilisation de la mémoire.

Générateur de la suite de Fibonacci

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • Ce code utilise un générateur pour produire séquentiellement les nombres de Fibonacci inférieurs à la limite supérieure, puis les affiche à l'aide d'une boucle for.

Cas d'utilisation

Les générateurs peuvent également être utilisés dans les scénarios suivants.

  • Traitement séquentiel de grands fichiers CSV ou de logs
  • Pagination d'API
  • Traitement de données en streaming (par exemple, Kafka, appareils IoT)

Résumé

Concept Point clé
yield Met en pause et renvoie une valeur
Fonction génératrice Fonction qui contient yield et retourne un itérateur lorsqu'elle est appelée
Avantages Économe en mémoire et idéale pour le traitement de grands ensembles de données
Expression génératrice Permet une syntaxe concise comme (x for x in iterable)

En utilisant les générateurs, vous pouvez traiter efficacement de grands ensembles de données tout en économisant de la mémoire et en gardant votre code concis.

Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.

YouTube Video