Python liste append : optimiser vos listes pour gagner en performance

On a tous eu ce script qui mouline pendant des minutes à construire une liste élément par élément dans une boucle for. Le réflexe, c’est list.append(). La méthode fonctionne, mais sur des volumes conséquents, elle n’est pas toujours le bon choix. Voici comment utiliser Python liste append à bon escient, et surtout quand passer à autre chose.

Mécanique mémoire de list.append en Python

Quand on appelle .append() sur une liste Python, CPython n’alloue pas la mémoire case par case. Le tableau interne est surdimensionné selon un facteur de croissance (proche de 1.125 pour les grandes listes). Chaque ajout est donc amorti en O(1), mais les réallocations ponctuelles copient l’intégralité du tableau existant vers un nouveau bloc plus grand.

A voir aussi : Webmail académie de Rouen : comment optimiser son utilisation ?

En pratique, cela signifie que la mémoire réellement occupée par une liste dépasse souvent le strict nécessaire. Sur une liste de quelques dizaines d’éléments, c’est négligeable. Sur plusieurs centaines de milliers d’entrées, le surplus mémoire devient mesurable et peut peser dans un environnement contraint (conteneur Docker avec limite RAM, par exemple).

Ce que renvoie append

.append() modifie la liste en place et renvoie None. Un piège classique consiste à écrire nouvelle = ma_liste.append(x) en pensant récupérer la liste modifiée. On se retrouve avec None dans nouvelle et un bug silencieux. Si on a besoin d’un chaînage, mieux vaut passer par la concaténation ou une compréhension.

A lire également : Pip Install -u dans un pipeline CI/CD : intégrer les mises à jour Python en continu

Développeuse travaillant sur l'optimisation de listes Python depuis un studio à domicile décontracté

Append en boucle vs compréhension de liste : benchmark terrain

Sur un cas courant (transformer chaque élément d’un itérable et stocker le résultat), on compare souvent ces deux approches :

result = []
for x in data: result.append(f(x))

contre :

result = [f(x) for x in data]

D’après les benchmarks relayés par Raymond Hettinger (core developer CPython, présentations PyCon 2019 et suivantes), la compréhension de liste est systématiquement plus rapide que l’appel à append dans une boucle explicite. La raison : moins d’opcodes exécutés par l’interpréteur, et un bytecode optimisé (LIST_APPEND directement dans la boucle interne au lieu du lookup de méthode + appel).

La différence devient significative dès que le nombre d’éléments dépasse quelques dizaines de milliers. En dessous, on ne verra rien dans un profiling sérieux. Au-delà, on peut gagner un facteur notable sur le temps d’exécution rien qu’en remplaçant la boucle par une compréhension.

Et list.extend dans tout ça

list.extend(iterable) est le bon réflexe quand on ajoute plusieurs éléments d’un coup plutôt qu’un par un. Comme la compréhension, extend limite les allers-retours dans la boucle d’interpréteur. On l’utilise typiquement quand on agrège des résultats par lots (lecture par chunks d’un fichier, réponses paginées d’une API).

  • append(x) : ajoute un seul élément à la fin, adapté aux insertions ponctuelles ou conditionnelles
  • extend(iterable) : ajoute tous les éléments d’un itérable, plus rapide que des appels append répétés
  • Compréhension de liste : construit la liste d’un bloc, le plus performant quand on part de zéro avec une transformation simple

Piège courant : append d’objets volumineux en data science

Dans un pipeline de données, on voit régulièrement ce pattern :

frames = []
for chunk in pd.read_csv('gros_fichier.csv', chunksize=10000):
    frames.append(chunk)
df = pd.concat(frames)

Chaque append ajoute une référence au DataFrame partiel, mais tous ces objets coexistent en mémoire jusqu’au concat final. Sur un fichier de plusieurs millions de lignes, on se retrouve avec le double de la mémoire nécessaire au moment de la concaténation : les chunks d’un côté, le DataFrame final de l’autre.

La documentation de pandas recommande de préférer un itérateur unique ou de traiter chaque chunk puis de ne garder que le résultat agrégé (somme, moyenne, filtrage) plutôt que de stocker tous les morceaux dans une liste. Les retours varient sur ce point selon la taille des fichiers et la RAM disponible, mais le principe reste : ne pas accumuler d’objets lourds dans une liste si on peut l’éviter.

Vue aérienne d'un bureau de programmeur avec code Python liste append affiché sur écran et notes manuscrites

Gains de Python 3.11+ sur les appels append

Depuis Python 3.11, le « specializing adaptive interpreter » de CPython optimise automatiquement les opérations fréquentes. L’opcode LIST_APPEND fait partie des cas qui bénéficient de cette spécialisation. Selon les notes de version officielles de Python 3.11 et les benchmarks de l’équipe CPython, une boucle classique avec append sur un gros itérable tourne plus vite à code identique par rapport à Python 3.8 ou 3.9.

Concrètement, si on est coincé avec un pattern append en boucle qu’on ne peut pas réécrire facilement (logique conditionnelle complexe dans la boucle, par exemple), passer à Python 3.11+ apporte un gain gratuit. Pas une raison pour ignorer les compréhensions, mais un argument pour maintenir son runtime à jour.

Quand append reste le bon choix en Python

Tout ne se résout pas avec une compréhension de liste. append garde sa place dans plusieurs situations concrètes :

  • Ajout conditionnel avec logique complexe (if/elif/try imbriqués) où une compréhension deviendrait illisible
  • Construction incrémentale dans une boucle while avec condition d’arrêt dynamique
  • Implémentation de piles (LIFO) avec le couple append / pop, qui reste le pattern standard en Python
  • Prototypage rapide où la lisibilité prime sur la microseconde gagnée

La règle opérationnelle : si on peut exprimer toute la logique en une ligne de compréhension lisible, on le fait. Si la compréhension dépasse deux lignes ou nécessite des effets de bord, append dans une boucle explicite est plus maintenable.

Le vrai levier de performance sur les listes Python n’est pas de bannir append, mais de choisir le bon outil selon le volume et la complexité. Mettre à jour son interpréteur, profiler avant d’optimiser, et garder le code lisible pour l’équipe qui le reprendra dans six mois.

Les immanquables