Itérateurs et générateurs : comment traiter des données en consommant moins de mémoire

Itérateurs et générateurs : comment traiter des données en consommant moins de mémoire
3 min de lecture

Un script chargé de résumer le journal des paiements a fonctionné sans incident pendant un an, avant de échouer sur un fichier trop volumineux. Un tutoriel consacré aux itérateurs et aux générateurs de Python montre comment construire des chaînes de traitement qui lisent les données un enregistrement à la fois, sans les charger toutes en mémoire.

  • Un script de résumé de journal a échoué sur un fichier de 9 GB, faute de mémoire.
  • La cause vient de la liste, qui conserve tous les enregistrements en même temps.
  • Les itérateurs et les générateurs lisent les données un enregistrement à la fois.
  • Les traitements globaux, comme le tri, exigent encore de connaître tout le flux.

Dans le cas décrit, le script ouvrait le fichier, construisait une liste d’enregistrements analysés, filtrait les erreurs, puis affichait un tableau. Après un changement dans la rotation des journaux, une semaine de trafic s’est retrouvée dans un seul fichier de 9 GB. Le traitement nocturne s’est interrompu avec une MemoryError avant d’avoir produit le moindre résumé. Selon l’auteur, la logique d’analyse n’était pas en cause : le problème venait de la liste, qui oblige à conserver tous les enregistrements en même temps.

Le fonctionnement repose sur un contrat simple. Un itérable, comme une liste, une chaîne de caractères ou un fichier ouvert, peut fournir un itérateur, c’est-à-dire un curseur qui avance élément par élément et ne revient jamais en arrière. La boucle for appelle ces étapes automatiquement, et la fin de la séquence se signale par l’exception StopIteration, qui n’est pas une erreur. Un itérateur épuisé ne se réinitialise pas : une fonction qui renvoie « rien » lors d’une seconde lecture n’est pas forcément vide, elle a simplement déjà été consommée.

Le tutoriel recommande de commencer par l’objet fichier, qui est déjà un itérateur. Lire un fichier avec f.readlines() crée une liste de toutes les lignes, alors que for line in f ne lit qu’un bloc tamponné à la fois. Les fonctions génératrices, qui utilisent le mot-clé yield, permettent ensuite de découper l’analyse en étapes nommées. L’instruction yield from enchaîne plusieurs fichiers de rotation comme un seul flux, et les expressions génératrices, écrites entre parenthèses, servent aux petites transformations intermédiaires. L’auteur signale un piège : la partie la plus à gauche d’une expression génératrice est évaluée immédiatement.

Mesurer avant de faire confiance

Le tutoriel insiste aussi sur les points où la liste revient discrètement. Les appels list(), sorted(), un dictionnaire qui regroupe les enregistrements par identifiant de requête, ou la construction d’un DataFrame réintroduisent la totalité du flux en mémoire. Les générateurs ne sont pas non plus plus rapides que les listes : leur intérêt est une mémoire bornée et des étapes réorganisables. Pour les traitements en lot, Python 3.12 propose itertools.batched(), et une recette équivalente sur islice est donnée pour les versions antérieures.

Pour vérifier le comportement réel, l’auteur recommande le module tracemalloc, qui mesure la mémoire allouée par Python, en valeur courante et en pic. Sur un journal synthétique de 200 000 lignes, d’environ 17 MB, la version « liste » atteint un pic de 198 MB sur un ordinateur portable, contre 0,4 MB pour la version en flux. L’auteur précise qu’il s’agit des résultats d’une seule exécution, et non d’un benchmark. Il rappelle également que tracemalloc ne voit pas les allocations des extensions en C ni le cache du système.

Les limites du flux

Certains traitements ne peuvent pas être menés en flux. Un tri global, une déduplication exacte sur l’ensemble du fichier ou un percentile calculé sur toutes les valeurs de latence exigent de connaître tous les enregistrements. Dans ce cas, le tutoriel évoque un algorithme approximatif fondé sur un échantillon, ou un outil externe qui trie sur disque.

Le tutoriel précise enfin que cette approche reste en Python pur. Pandas, Spark, Dask ou les flux asynchrones répondent à des problèmes voisins, mais ne sont pas nécessaires pour un fichier de journal, un export ou un vidage de données qui tient sur le disque sans tenir en mémoire vive.

S’abonner
Notification pour
guest

0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
0
Nous aimerions avoir votre avis, veuillez laisser un commentaire.x