Imaginez que vous devez traiter un fichier journal de plusieurs gigaoctets. La solution la plus simple consiste à charger l'intégralité du fichier en mémoire, à le diviser en lignes et à commencer le traitement. Mais que se passe-t-il s'il n'y a pas assez de RAM ? Ou s'il y a des dizaines de fichiers de ce type ? C'est pour de telles situations que Python a des générateurs et des itérateurs — des outils puissants qui vous permettent de travailler avec de grandes quantités de données sans les charger complètement en mémoire.
Que sont les itérateurs ?
Un itérateur est un objet qui vous permet de parcourir les éléments d'une collection un par un sans charger toute la collection en mémoire à la fois. En Python, tout objet qui implémente la méthode __iter__() et __next__() est un itérateur.
Lorsque vous écrivez for item in collection, Python appelle implicitement iter(collection) pour obtenir un itérateur, puis appelle plusieurs fois next() pour obtenir l'élément suivant jusqu'à ce qu'une exception soit levée StopIteration.
# Exemple d'itérateur simple
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
# Utilisation
counter = CountDown(5)
for num in counter:
print(num) # Affichera : 5, 4, 3, 2, 1
Générateurs : itérateurs sous stéroïdes
Les générateurs sont un moyen simplifié de créer des itérateurs. Au lieu d'écrire une classe avec les méthodes __iter__() et __next__(), vous créez simplement une fonction avec le mot-clé yield. Le générateur enregistre automatiquement son état entre les appels et reprend l'exécution à l'endroit où il s'est arrêté.
def count_down(start):
while start > 0:
yield start
start -= 1
# Utilisation
for num in count_down(5):
print(num) # Affichera : 5, 4, 3, 2, 1L'avantage est évident : le code est devenu plus compact et plus clair, et la fonctionnalité est restée la même.
Pourquoi cela permet-il d'économiser de la mémoire ?
Le principal avantage des générateurs est le calcul paresseux. Les éléments ne sont créés que lorsqu'ils sont nécessaires et peuvent être immédiatement traités et oubliés. Prenons un exemple concret :
# Mauvais : tout est chargé en mémoire
def read_large_file_bad(file_path):
with open(file_path, 'r') as file:
lines = file.readlines() # Tout le fichier est en mémoire !
return [line.strip().upper() for line in lines]
# Bien : nous traitons ligne par ligne
def read_large_file_good(file_path):
with open(file_path, 'r') as file:
for line in file: # file est déjà un itérateur !
yield line.strip().upper()
# Utilisation
for processed_line in read_large_file_good('huge_log.txt'):
# Nous traitons une ligne à la fois
process(processed_line)Dans le premier cas, si le fichier pèse 2 Go, votre programme prendra au moins 2 Go de RAM. Dans le second cas, seulement quelques kilo-octets, quelle que soit la taille du fichier.

Exemples pratiques d'utilisation.
Traitement des données de l'API
Les API renvoient souvent des données page par page. Le générateur peut masquer cette complexité :
def fetch_all_users(api_client, page_size=100):
page = 1
while True:
users = api_client.get_users(page=page, size=page_size)
if not users:
break
for user in users:
yield user
page += 1
# Utilisation
for user in fetch_all_users(api):
print(user['name']) # Nous traitons les utilisateurs un par unSéquences infinies
Les générateurs vous permettent de créer des séquences infinies sans risque de débordement de mémoire :
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Vous pouvez prendre autant d'éléments que vous le souhaitez
from itertools import islice
first_ten = list(islice(fibonacci(), 10))
print(first_ten) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Convoyeurs de traitement de données
Les générateurs sont parfaits pour créer des chaînes de traitement :
def read_csv(file_path):
with open(file_path) as f:
for line in f:
yield line.strip().split(',')
def filter_by_status(rows, status):
for row in rows:
if row[2] == status: # statut dans la troisième colonne
yield row
def extract_emails(rows):
for row in rows:
yield row[1] # e-mail dans la deuxième colonne
# Assemblage du convoyeur
rows = read_csv('users.csv')
active_users = filter_by_status(rows, 'active')
emails = extract_emails(active_users)
# C'est seulement ici que le vrai traitement commence
for email in emails:
send_notification(email)La beauté de cette approche est que chaque étape du pipeline est indépendante et testable, et la mémoire n'est utilisée que pour un seul élément.
Expressions génératrices
En plus des fonctions génératrices, Python prend en charge les expressions génératrices, une syntaxe compacte pour créer des générateurs :
# List comprehension — crée une liste en mémoire
squares_list = [x**2 for x in range(1000000)] # Prend beaucoup de mémoire
# Generator expression — crée un générateur
squares_gen = (x**2 for x in range(1000000)) # Ne prend presque pas de mémoire
# Peut être utilisé dans les fonctions
total = sum(x**2 for x in range(1000000)) # Efficace !Veuillez noter que des parenthèses sont utilisées à la place des crochets. C'est l'un des changements les plus simples et les plus efficaces que vous puissiez apporter à votre code pour économiser de la mémoire.
Quand ne faut-il PAS utiliser de générateurs ?
Malgré tous les avantages, les générateurs ne sont pas toujours adaptés :
Lorsque vous avez besoin d'un accès multiple aux données : le générateur ne peut être passé qu'une seule fois. Si vous avez besoin d'accéder aux données plusieurs fois, il est préférable d'utiliser une liste.
Lorsque la vitesse d'accès est importante : l'élément est obtenu instantanément par index à partir de la liste, et le générateur devra être parcouru depuis le début.
Lorsque les données sont placées en mémoire : si vous avez un petit ensemble de données, les frais généraux de création d'un générateur peuvent être injustifiés.
Quand vous avez besoin de connaître la longueur : len() ne fonctionne pas avec les générateurs, et le calcul de la longueur nécessite une recherche exhaustive.
Techniques avancées.
Délégation de générateurs
Avec l'aide de yield from, vous pouvez déléguer l'exécution à un autre générateur :
def read_multiple_files(*file_paths):
for path in file_paths:
with open(path) as f:
yield from f # Déléguer la lecture ligne par ligne
# Lire plusieurs fichiers comme un seul flux
for line in read_multiple_files('log1.txt', 'log2.txt', 'log3.txt'):
process(line)Communication bidirectionnelle avec les générateurs
Les générateurs peuvent non seulement donner des valeurs par le biais de yield, mais aussi les accepter par la méthode send() :
def running_average():
total = 0
count = 0
average = None
while True:
value = yield average
total += value
count += 1
average = total / count
# Utilisation
avg = running_average()
next(avg) # Démarrage du générateur
print(avg.send(10)) # 10.0
print(avg.send(20)) # 15.0
print(avg.send(30)) # 20.0Mesurer l'efficacité
Voyons la différence réelle dans l'utilisation de la mémoire :
import sys
# Liste
numbers_list = [x for x in range(1000000)]
print(f"List: {sys.getsizeof(numbers_list) / 1024 / 1024:.2f} MB")
# Générateur
numbers_gen = (x for x in range(1000000))
print(f"Generator: {sys.getsizeof(numbers_gen) / 1024:.2f} KB")Sur ma machine, le résultat montre une différence de milliers de fois : la liste prend environ 8 Mo et le générateur moins de 0,1 Ko.
Conclusion
Les générateurs et les itérateurs ne sont pas seulement du sucre syntaxique, mais un outil fondamental pour travailler avec de grandes quantités de données. Ils vous permettent d'écrire un code plus efficace qui s'adapte à n'importe quelle taille de données. Comprendre quand et comment utiliser les générateurs distingue un développeur expérimenté d'un débutant.
Commencez simplement : remplacez les compréhensions de liste par des expressions de générateur là où vous n'avez pas besoin d'un accès multiple aux données. Essayez ensuite d'écrire une fonction génératrice pour traiter les fichiers. Et progressivement, vous constaterez que vous pensez aux flux de données, et non aux collections en mémoire.
Annexe Code propose des cours structurés pour les développeurs débutants, couvrant Python, JavaScript, HTML, CSS et de nombreuses autres technologies.
Rejoignez notre Chaîne Telegram, où vous trouverez le soutien de la communauté, des ressources supplémentaires et des réponses aux questions de développeurs expérimentés.
Apprenez à programmer efficacement et avec plaisir !
