Cours de Python / Filtrer et transformer des collections par compréhension

Une compréhension construit une collection en transformant des éléments ou en conservant ceux qui satisfont une condition. Utilisez-la pour une transformation courte et lisible ; préférez une boucle ordinaire lorsque plusieurs étapes, une gestion d’erreurs ou des effets de bord doivent rester explicites.

Séparer la transformation du filtre

La forme [expression for item in source if condition] crée une liste. Python parcourt la source, teste la condition, puis évalue l’expression de résultat pour chaque élément accepté. Dans [x * 2 for x in range(4) if x % 2 == 0], le filtre conserve zéro et deux ; la multiplication produit donc [0, 4].

Le if final est un filtre : il peut exclure un élément. À l’inverse, a if condition else b placé dans l’expression choisit une valeur pour un élément déjà retenu. Dans le programme ci-dessous, le premier mécanisme élimine les montants négatifs ; le second associe une étiquette à chacun des montants conservés. La documentation Python des listes en compréhension montre leur correspondance avec des boucles ordinaires.

Construire une nouvelle liste ne modifie pas à lui seul la liste extérieure source et ne constitue pas une copie profonde. Si l’expression conserve un dictionnaire existant, les deux collections peuvent encore référencer le même objet modifiable. Une expression qui appelle une fonction modifiant la source peut également avoir des effets de bord : la concision de la syntaxe ne garantit pas leur absence.

Créer une liste, un dictionnaire et un ensemble

Ce programme utilise Python 3 et sa bibliothèque standard. Enregistrez son bloc complet dans comprehensions.py, en UTF-8, dans un dossier d’exercice. Dans un terminal ouvert dans ce dossier, avec l’environnement virtuel du projet activé, exécutez python comprehensions.py. Sur macOS ou Ubuntu hors environnement virtuel, utilisez python3 comprehensions.py si c’est le nom de votre interpréteur.

amounts = [3500, -1, 8000, 0]
valid = [x for x in amounts if x >= 0]
labels = ["paid" if x > 0 else "free" for x in valid]
records = [("food", 3500), ("bus", 2500)]
by_name = {name: amount for name, amount in records}
remainders = {x % 3 for x in valid}
print(valid)
print(labels)
print(by_name)
print(sorted(remainders))

Le résultat attendu est :

[3500, 8000, 0]
['paid', 'paid', 'free']
{'food': 3500, 'bus': 2500}
[0, 2]

valid conserve les montants non négatifs dans l’ordre de départ, y compris zéro. labels contient une étiquette pour chaque montant retenu : paid signifie « payant » et free « gratuit ». by_name associe les noms à leurs montants. Enfin, les restes de 3500 et de 8000 dans une division par trois valent tous deux deux ; le reste de zéro vaut zéro. L’ensemble ne contient donc que zéro et deux, affichés dans un ordre stable grâce à sorted().

Comprendre les doublons et les objets partagés

Les crochets produisent une liste qui peut conserver des répétitions. La forme {name: amount for name, amount in records} produit un dictionnaire : lorsqu’une clé apparaît plusieurs fois, sa dernière valeur remplace la précédente. Ajouter ("food", 1000) à la fin de records donne ainsi 1000 pour food, pas 4500. Pour calculer un total, utilisez une accumulation explicite, comme dans la leçon sur les totaux par catégorie avec un dictionnaire.

La forme {x % 3 for x in valid}, sans deux-points, produit un ensemble. Elle supprime les valeurs en double ; elle ne préserve ni le nombre de transactions ni un ordre de parcours destiné à l’affichage. Ne l’utilisez donc pas pour représenter des opérations répétées dont chacune doit compter. Les règles des ensembles et des opérations ensemblistes s’appliquent aussi à cette construction.

Pour observer le partage de références, partez d’une liste de dictionnaires et conservez chacun d’eux avec une compréhension de forme [item for item in source]. Modifier un champ d’un dictionnaire dans le résultat affectera aussi ce dictionnaire vu depuis la source. Remplacer seulement un élément de la nouvelle liste ne remplace pas l’élément correspondant de l’ancienne. Définissez le niveau de copie nécessaire plutôt que de supposer que « nouvelle collection » signifie « tous les objets sont indépendants ».

Tester les filtres et choisir une boucle lisible

  1. Dans la ligne qui construit valid, changez uniquement x >= 0 en x > 0. Le résultat devient [3500, 8000], les étiquettes deviennent ['paid', 'paid'] et les restes affichés deviennent [2]. Rétablissez ensuite >=.
  2. Ajoutez seulement ("food", 1000) à la fin de records. Vérifiez que la valeur de food est remplacée sans être additionnée, puis supprimez cet ajout pour retrouver le dictionnaire initial.
  3. Remplacez uniquement amounts par [-1, -2]. Les listes valid et labels ainsi que la liste des restes affichée sont vides ; le dictionnaire reste inchangé, car il provient de records. Restaurez les quatre montants initiaux et comparez les quatre lignes de référence.

Un résultat vide demande une règle explicite avant de lire son premier élément ou de calculer une moyenne : l’indice zéro lèverait une IndexError et une division par sa longueur nulle une ZeroDivisionError. Ne transformez pas arbitrairement « aucune donnée » en une moyenne égale à zéro si ces situations ont des sens différents.

Pour apprendre le déroulement, écrivez d’abord la règle avec une liste résultat initialement vide, une boucle for, un test puis append. Le parcours suit les règles des boucles for et des itérables. Si la règle se résume à un filtre et une transformation, la compréhension peut être plus lisible. Si le but est seulement d’imprimer ou d’enregistrer des éléments, gardez la boucle : une liste de résultats inutilisés rendrait l’intention moins claire et consommerait de la mémoire sans besoin.