Files
org-roamings/20220529123824-generator.org

3.8 KiB

Generator

Introduction

Un generator est :

  • Une fonction retournant un iterable,
  • Un iterable est un object implémentant les méthodes __iter__ et __next__ et génère une erreur StopIteration lorsqu'il n'y a plus de valeur à retourner,
  • Utilise le statement yield au lieu de return.

Le statement return termine complètement une fonction alors que yield la suspend (sauvegarde de son contexte afin de continuer là où elle en était lors de son prochain appel).

Différences entre une function et un generator :

  • Présence d'au moins un yield dans un generator,
  • Le generator retourne un iterator (les méthodes __iter__ et __next__ sont implémentées automatiquement),
  • Sauvegarde du contexte d'un generator,
  • L'erreur StopIteration est générée automatiquement lors qu'un generator termine.

Les generator sont appropriés quand il est nécessaire de produire un flux de données infini.

def simple_generator():
    for num in range(1, 4):
        print(num)
        yield num

gen = simple_generator()
next(gen)
next(gen)
next(gen)
try:
    next(gen)
except StopIteration as __:
    print('StopIteration raised')

for num in simple_generator():
    pass
1
2
3
StopIteration raised
1
2
3

Un generator peut être créé soit en déclarant une fonction (cf. exemple précédent) ou directement via une generator expression :

list_numbers = [10, 18, 13, 23]
list_square = [x**2 for x in list_numbers]

generator = (x**2 for x in list_numbers)

print(list_square)
print(generator)
[100, 324, 169, 529]
<generator object <genexpr> at 0x7fae7b90bb30>

Différences avec list

La principale différence entre les list comprehension et les generator expression consiste dans le fait que l'ensemble de la list est créée par la list compréhension alors que seul un generator est produit par la generator expression. Cela apporte quelques avantages :

  • Chaque résultat du generator n'est produit que suite à l'appel de __next__ (lazy execution),
  • L'espace mémoire nécessaire s'en retrouve réduit,

Il peut s'avérer plus efficace d'utiliser un generator lorsque l'ensemble des résultats ne sera pas utilisé à l'inverse pour les list comprehension.

Enchainement de generator

Il est possible d'enchainer plusieurs generator :

def fibonacci_numbers(numbers):
    x, y = 0, 1
    for _ in range(numbers):
        x, y = y, x + y
        yield x

def square(numbers):
    for num in numbers:
        yield num**2

print(f'{sum(square(fibonacci_numbers(30))) = }')
sum(square(fibonacci_numbers(30))) = 1120149658760

Renvoyer une valeur à un generator

def dummy_generator():
    i = 0
    for i in range(10):
        print(f'from generator {yield i}')

generator = dummy_generator()
received = generator.send(None)
print(f'from here {received}')
try:
    while True:
        received = generator.send(received + 100)
        print(f'from here {received}')
except StopIteration:
    pass
from here 0
from generator 100
from here 1
from generator 101
from here 2
from generator 102
from here 3
from generator 103
from here 4
from generator 104
from here 5
from generator 105
from here 6
from generator 106
from here 7
from generator 107
from here 8
from generator 108
from here 9
from generator 109