J’arrange souvent du code pour des sites à fort trafic et j’ai appris qu’optimiser la façon dont on parcourt des données change tout. Dans mes projets, *Python* m’a sauvé la mise grâce aux itérateurs et aux générateurs : ils permettent d’itérer *un élément à la fois*, de réduire l’empreinte mémoire et d’écrire des boucles plus propres. Ici je vous guide pas à pas — définitions, exemples pratiques, pièges à éviter et cas concrets (traitement de logs, flux CSV, génération infinie). Vous repartirez avec des recettes réutilisables et des snippets testés en production.
- Itérateur : objet avec __iter__ et __next__.
- Générateur : fonction utilisant yield pour une évaluation paresseuse.
- Expression génératrice : syntaxe compacte à base de parenthèses pour économiser la mémoire.
- Cas d’usage : lecture de gros fichiers, pipelines streaming, suites infinies.
- Astuce rapide : utilisez next() pour consommer un élément à la demande.
Réponse rapide : Pour créer un itérateur, implémentez __iter__ et __next__ dans une classe. Pour un générateur compact et paresseux, écrivez une fonction avec yield. Utilisez iter() et next() pour manipuler des itérables existants et préférez les générateurs pour traiter de grands flux sans tout charger en mémoire.
Comprendre iter(), next() et yield : notions clés pour créer des itérateurs et générateurs
La mécanique interne d’une boucle for repose sur deux concepts simples : appeler iter() sur un objet itérable pour obtenir un itérateur, puis appeler next() jusqu’à ce qu’une StopIteration soit levée.
Concrètement, une liste est un itérable ; iter(ma_liste) renvoie un objet que l’on peut consommer avec next(). Voilà pourquoi la boucle for est à la fois lisible et sûre : elle gère l’exception en coulisse.
Insight : maîtriser iter() et next() vous donne un contrôle fin sur le flux de données et facilite le débogage.

Créer un itérateur personnalisé (implémenter __iter__ et __next__)
Quand on a besoin d’une logique d’itération spécifique, on implémente une classe avec __iter__ et __next__. Je me souviens d’un micro-service qui lisait des logs énormes : j’ai évité des OOM en écrivant un itérateur sur-mesure.
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
result = self.data[self.index]
self.index += 1
return result
Utilisez for x in MyIterator([…]) ou next() pour consommer un élément à la fois. Si vous manipulez des collections, pensez à jeter un oeil aux techniques pour manipuler des listes en Python et à l’usage de range et enumerate pour des boucles robustes.
Insight : un itérateur personnalisé rend explicite l’état de l’itération et facilite l’optimisation mémoire.
Générateurs et yield : écrire des fonctions paresseuses, simples et performantes
Un générateur est une fonction qui utilise yield pour retourner une valeur sans perdre son état. Lorsque vous appelez la fonction, elle renvoie un objet générateur qui produit les éléments au fur et à mesure.
J’ai remplacé des listes temporaires par des générateurs dans une pipeline ETL ; le temps de traitement a chuté et la mémoire s’est stabilisée.
Insight : préférez les générateurs pour des flux volumiques ou potentiellement infinis.

Exemples pratiques : carrés et pipeline
Fonction génératrice simple :
def my_generator(data):
for x in data:
yield x**2
Lecture paresseuse d’un fichier (schéma) :
def read_lines(path):
with open(path) as f:
for line in f:
yield line.strip()
Ces snippets permettent de composer des pipelines lisibles et testables. Pour transformer ou filtrer rapidement des séquences, les expressions génératrices sont souvent plus compactes.
Insight : avec yield, la fonction « suspend » son exécution et reprend exactement là où elle s’était arrêtée.
Cas avancé : générateur de nombres premiers
Un générateur de nombres premiers illustre bien l’état conservé entre appels :
def _is_prime(n):
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
def prime_numbers(n):
for i in range(2, n+1):
if _is_prime(i):
yield i
J’ai utilisé ce pattern pour générer des identifiants uniques dans un prototype : clair, testable, et performant quand on n’a pas besoin de la liste complète.
Insight : les générateurs permettent d’exprimer proprement une logique séquentielle compliquée sans sacrifier la lisibilité.

Expressions génératrices vs compréhensions : mémoire, rapidité, usages
Une expression génératrice ressemble à une compréhension de liste mais renvoie un objet générateur évalué à la demande. Utilisez-la quand la mémoire importe.
Exemple :
squares_list = [x**2 for x in range(1, 6)] # crée toute la liste
squares_gen = (x**2 for x in range(1, 6)) # paresseux
- Avantage mémoire : l’expression génératrice n’alloue pas la liste entière.
- Lisibilité : souvent identique en complexité cognitive aux compréhensions.
- Performance : les compréhensions peuvent être plus rapides pour de petites tailles.
Dans mes tests, pour des millions d’éléments, la différence mémoire devient décisive ; pour des petites séquences, la compréhensions reste pratique.
Insight : choisissez entre les deux selon la taille des données et la contrainte mémoire.
Bonnes pratiques et pièges à éviter
- Ne pas matérialiser inutilement un générateur avec list() si vous n’avez besoin que d’itérer.
- Garder les fonctions pures : évitez les effets de bord dans les générateurs qui complexifient les tests.
- Fermer les générateurs (generator.close()) si vous interrompez prématurément des ressources (fichiers, sockets).
- Logger l’état des itérateurs lors du débogage pour éviter des boucles infinies.
Si vous cherchez des outils complémentaires pour travailler les listes ou améliorer vos fonctions, consultez des ressources pratiques comme créer et modifier des listes en Python ou des snippets utiles sur exemples de code Python.
Insight : une petite routine de tests unitaires pour vos itérateurs/générateurs évite des surprises en production.
Checklist rapide pour intégrer itérateurs et générateurs en production
- Identifier les flux volumineux ou infinis (logs, CSV) à traiter paresseusement.
- Préférer générateurs pour les pipelines et itérateurs personnalisés pour une logique d’accès particulière.
- Mesurer mémoire et temps : utilisez des profils avant/après.
- Documenter l’API : indiquez si une fonction retourne une liste ou un générateur.
Insight : documenter le comportement paresseux évite de transformer accidentellement un générateur en liste inutilement.
Comment obtenir le prochain élément d’un itérateur ?
Utilisez la fonction next() sur l’objet itérateur. Si la séquence est terminée, une exception StopIteration est levée — utilisez try/except ou une boucle for qui gère cela automatiquement.
Quand préférer un générateur à une liste ?
Privilégiez un générateur lorsque les données sont volumineuses ou potentiellement infinies, ou si vous voulez composer des étapes de traitement sans matérialiser toute la séquence en mémoire.
Peut-on envoyer des valeurs à un générateur ?
Oui : les générateurs supportent send() pour fournir des valeurs à la fonction suspendue, ce qui ouvre des patterns avancés (coroutines légères). Utilisez-les avec précaution et documentez bien le comportement.
Les générateurs sont-ils plus rapides que les itérateurs classes ?
Les générateurs sont généralement plus simples et souvent plus rapides à écrire et à exécuter pour des cas standards, mais le choix dépend du contexte ; les itérateurs classes restent utiles pour des états complexes et des APIs explicites.

