# Pattern classique : boucle + append
notes = [12, 15, 9, 17, 11]
carres = []
for n in notes:
carres.append(n ** 2)
print(carres)[144, 225, 81, 289, 121]
Les compréhensions sont probablement la construction la plus emblématique de Python. Elles permettent d’écrire en une ligne ce qui demanderait 4 ou 5 lignes avec une boucle classique. Maîtrisées au TOSA Avancé, elles font la différence entre un débutant et un Pythoniste.
Vous avez déjà utilisé ce pattern dans la Partie 2, sans qu’on le nomme :
# Pattern classique : boucle + append
notes = [12, 15, 9, 17, 11]
carres = []
for n in notes:
carres.append(n ** 2)
print(carres)[144, 225, 81, 289, 121]
La compréhension de liste exprime la même chose en une ligne :
notes = [12, 15, 9, 17, 11]
carres = [n ** 2 for n in notes]
print(carres)[144, 225, 81, 289, 121]
C’est plus court, plus lisible, et plus rapide (optimisations internes de Python).
[ expression for element in iterable if condition ]
└────┬────┘ └────────┬──────────┘ └─────┬──────┘
transforme parcourt chaque filtre
Les trois parties :
expression : ce qu’on produit pour chaque élément retenu.for element in iterable : d’où viennent les éléments.if condition (optionnelle) : garde seulement les éléments vérifiant la condition.nombres = [1, 2, 3, 4, 5]
# Doubler
doubles = [n * 2 for n in nombres]
print(doubles)
# Carrés
carres = [n ** 2 for n in nombres]
print(carres)
# Conversion de types
chaines = [str(n) for n in nombres]
print(chaines)[2, 4, 6, 8, 10]
[1, 4, 9, 16, 25]
['1', '2', '3', '4', '5']
ifnombres = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Seulement les pairs
pairs = [n for n in nombres if n % 2 == 0]
print(pairs)
# Pairs multipliés par 10
pairs_x10 = [n * 10 for n in nombres if n % 2 == 0]
print(pairs_x10)
# Chaînes non vides
mots = ["Alice", "", "Bob", " ", "Charlie"]
non_vides = [m for m in mots if m.strip()]
print(non_vides)[2, 4, 6, 8, 10]
[20, 40, 60, 80, 100]
['Alice', 'Bob', 'Charlie']
On peut placer une ternaire dans l’expression (attention, ce n’est plus un filtre !) :
notes = [12, 15, 9, 17, 7, 11]
# Étiqueter chaque note
mentions = ["admis" if n >= 10 else "recalé" for n in notes]
print(mentions)['admis', 'admis', 'recalé', 'admis', 'recalé', 'admis']
if avant ou après for ?
Il y a deux types de if dans une compréhension — attention à ne pas les confondre :
Position du if |
Rôle | Syntaxe |
|---|---|---|
Après for (à droite) |
Filtre | [n for n in l if cond] |
Avant for (ternaire dans l’expression) |
Choix de valeur | [a if cond else b for n in l] |
# Filtrer : ne garde que les pairs
print([n for n in range(6) if n % 2 == 0])
# Choisir : transformer selon condition
print(["pair" if n % 2 == 0 else "impair" for n in range(6)])
# Combiner les deux :
# filtrer (ne garder que > 5) ET transformer (* 10 ou * 100)
print([n * 10 if n > 7 else n * 100 for n in range(6, 12) if n != 10])[0, 2, 4]
['pair', 'impair', 'pair', 'impair', 'pair', 'impair']
[600, 700, 80, 90, 110]
for)On peut chaîner plusieurs for pour produire toutes les combinaisons :
# Produit cartésien
couleurs = ["rouge", "vert"]
tailles = ["S", "M", "L"]
combinaisons = [f"{c}-{t}" for c in couleurs for t in tailles]
print(combinaisons)['rouge-S', 'rouge-M', 'rouge-L', 'vert-S', 'vert-M', 'vert-L']
Équivalent à deux boucles imbriquées :
combinaisons = []
for c in couleurs:
for t in tailles:
combinaisons.append(f"{c}-{t}")Un idiome classique :
matrice = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
aplatie = [x for ligne in matrice for x in ligne]
print(aplatie)[1, 2, 3, 4, 5, 6, 7, 8, 9]
for : comme une lecture de gauche à droite
Dans [x for ligne in matrice for x in ligne], lisez de gauche à droite comme un code classique :
for ligne in matrice:
for x in ligne:
# ajouter x au résultatC’est toujours le for le plus à gauche qui est le plus externe.
Même principe, mais on crée un dictionnaire avec {clé: valeur}.
noms = ["Alice", "Bob", "Charlie"]
# Créer un dict à partir d'une liste
longueurs = {nom: len(nom) for nom in noms}
print(longueurs){'Alice': 5, 'Bob': 3, 'Charlie': 7}
prix_ht = {"pain": 1.20, "lait": 0.95, "fromage": 4.50}
# Ajouter 20% de TVA
prix_ttc = {produit: prix * 1.20 for produit, prix in prix_ht.items()}
print(prix_ttc)
# Arrondir à 2 décimales
prix_ttc_arrondis = {p: round(pv, 2) for p, pv in prix_ttc.items()}
print(prix_ttc_arrondis){'pain': 1.44, 'lait': 1.14, 'fromage': 5.3999999999999995}
{'pain': 1.44, 'lait': 1.14, 'fromage': 5.4}
d = {"a": 1, "b": 2, "c": 3}
inverse = {v: k for k, v in d.items()}
print(inverse){1: 'a', 2: 'b', 3: 'c'}
notes = {"Alice": 14, "Bob": 8, "Charlie": 17, "Diana": 9, "Eve": 15}
# Ne garder que les admis
admis = {nom: n for nom, n in notes.items() if n >= 10}
print(admis){'Alice': 14, 'Charlie': 17, 'Eve': 15}
Mêmes règles, avec {...} sans : :
mots = ["chat", "chien", "oiseau", "chat", "lapin", "chien"]
# Ensemble des mots uniques
uniques = {m for m in mots}
print(uniques)
# Longueurs uniques
longueurs = {len(m) for m in mots}
print(longueurs)
# Caractères uniques d'un texte
texte = "anticonstitutionnellement"
lettres = {c for c in texte}
print(lettres){'chien', 'chat', 'lapin', 'oiseau'}
{4, 5, 6}
{'t', 'e', 's', 'o', 'm', 'n', 'a', 'l', 'i', 'u', 'c'}
{} vs compréhension
{1, 2, 3} → set.{"a": 1} → dict.{} → dict vide (pas set vide !).{n for n in ...} → set (compréhension de set).{n: n**2 for n in ...} → dict (compréhension de dict, présence du :).La présence du : dans l’expression distingue dict et set.
Une expression génératrice s’écrit comme une compréhension de liste, mais avec des parenthèses (...) au lieu de crochets.
# Compréhension de liste : crée TOUTE la liste en mémoire
l = [n ** 2 for n in range(10)]
print(l)
# Expression génératrice : évalue À LA DEMANDE
g = (n ** 2 for n in range(10))
print(g)[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x000001B414693510>
Les générateurs ne stockent pas tous les éléments en mémoire — ils les produisent un à un pendant l’itération. Avantages :
# Comparaison mémoire
import sys
l = [n ** 2 for n in range(1_000_000)]
g = (n ** 2 for n in range(1_000_000))
print(f"Liste : {sys.getsizeof(l):,} octets")
print(f"Générateur : {sys.getsizeof(g):,} octets")Liste : 8,448,728 octets
Générateur : 200 octets
Quand on passe une compréhension à une fonction comme sum, min, max, any, all… les parenthèses de la fonction font office de parenthèses du générateur.
nombres = [1, 2, 3, 4, 5]
# Somme des carrés — sans liste intermédiaire
print(sum(n ** 2 for n in nombres))
# Tous les éléments positifs ?
print(all(n > 0 for n in nombres))
# Au moins un pair ?
print(any(n % 2 == 0 for n in nombres))55
True
True
Pas besoin d’écrire sum([n ** 2 for n in nombres]) — c’est moins efficace (crée une liste temporaire inutile).
yield et les vrais générateurs (aperçu)Les fonctions génératrices avec yield sont traitées au niveau Expert. Aperçu pour votre culture :
def carres_jusqu_a(n):
"""Renvoie les carrés de 0 à n-1."""
for i in range(n):
yield i ** 2 # yield au lieu de return
# Utilisation
g = carres_jusqu_a(5)
print(list(g))[0, 1, 4, 9, 16]
On approfondira ce sujet en Partie 4.
Les compréhensions sont géniales, mais pas universelles. Évitez-les quand :
# ❌ Illisible — mieux vaut une boucle explicite
resultat = [traiter(x) if x > 0 else autre_traitement(x) if x == 0 else fallback(x)
for x in donnees if pre_filtre(x)]Règle du pouce : si votre compréhension fait plus d’une ligne, préférez une boucle classique.
Les compréhensions sont faites pour construire une collection, pas pour des actions annexes (afficher, enregistrer…). Ne mettez pas print() dans une compréhension.
# ❌ Mauvais usage
[print(n) for n in [1, 2, 3]] # crée une liste de None inutilePréférez une boucle claire :
for n in [1, 2, 3]:
print(n)1
2
3
Si vous voulez modifier un dict existant, pas de compréhension.
Les compréhensions sont typiquement 30 à 50 % plus rapides que les boucles équivalentes, car Python les optimise en interne.
import timeit
n = 10000
t_boucle = timeit.timeit('''
carres = []
for i in range(n):
carres.append(i * i)
''', globals={"n": n}, number=1000)
t_compr = timeit.timeit('[i * i for i in range(n)]',
globals={"n": n}, number=1000)
print(f"Boucle : {t_boucle*1000:.1f} ms")
print(f"Compréhension: {t_compr*1000:.1f} ms")
print(f"Gain : {(1 - t_compr/t_boucle)*100:.0f}% plus rapide")Boucle : 368.7 ms
Compréhension: 315.3 ms
Gain : 14% plus rapide
Que renvoie [x * 2 for x in range(4)] ?
[0, 2, 4, 6][0, 1, 2, 3][2, 4, 6, 8][0, 2, 4, 6, 8]a) [0, 2, 4, 6] — range(4) donne 0, 1, 2, 3, chacun multiplié par 2.
print([x * 2 for x in range(4)])[0, 2, 4, 6]
Que renvoie [n for n in range(10) if n % 3 == 0] ?
[0, 3, 6, 9][3, 6, 9][1, 2, 4, 5, 7, 8][0, 1, 2, 3, 4, 5, 6, 7, 8, 9]a) [0, 3, 6, 9] — on garde les multiples de 3 entre 0 et 9 (inclus). Attention : 0 est divisible par 3.
print([n for n in range(10) if n % 3 == 0])[0, 3, 6, 9]
Que produit {x: x**2 for x in range(1, 5)} ?
{1: 1, 2: 4, 3: 9, 4: 16}{1, 4, 9, 16}[1, 4, 9, 16]a) {1: 1, 2: 4, 3: 9, 4: 16} — compréhension de dict : {clé: valeur for ...}. Le : dans l’expression indique qu’on crée un dict.
print({x: x**2 for x in range(1, 5)}){1: 1, 2: 4, 3: 9, 4: 16}
Quelle différence entre [n for n in range(3)] et (n for n in range(3)) ?
c) — crochets = liste (créée en mémoire), parenthèses = générateur (évaluation paresseuse, très économe en mémoire).
print(type([n for n in range(3)]))
print(type((n for n in range(3))))<class 'list'>
<class 'generator'>
Que produit [i + j for i in range(2) for j in range(3)] ?
[0, 1, 2, 1, 2, 3][0, 1, 2][1, 2, 3]a) [0, 1, 2, 1, 2, 3] — pour i=0 : 0+0, 0+1, 0+2 = 0, 1, 2. Pour i=1 : 1+0, 1+1, 1+2 = 1, 2, 3.
print([i + j for i in range(2) for j in range(3)])[0, 1, 2, 1, 2, 3]
Quelle est la bonne syntaxe pour « n ou -n selon la parité » ?
[n if n % 2 == 0 else -n for n in range(5)][n for n in range(5) if n % 2 == 0 else -n][n for n in range(5) if n % 2 == 0][if n % 2 == 0 n else -n for n in range(5)]a) — la ternaire A if cond else B vient avant le for. Piège classique : la forme if cond else dans la partie de gauche (transformation), la forme if cond à droite du for (filtre).
print([n if n % 2 == 0 else -n for n in range(5)])[0, -1, 2, -3, 4]
Quelle compréhension aplatit [[1, 2], [3, 4]] en [1, 2, 3, 4] ?
[x for x in matrice][x for ligne in matrice for x in ligne][x for x in ligne for ligne in matrice][flatten(matrice)]b) — l’ordre des for compte : le plus externe (gauche) en premier. Lisez-la comme for ligne in matrice: for x in ligne: ajoute x.
matrice = [[1, 2], [3, 4]]
print([x for ligne in matrice for x in ligne])[1, 2, 3, 4]
Que renvoie sum(n for n in range(5)) ?
1015b) 10 — somme de 0+1+2+3+4. C’est une expression génératrice passée directement à sum ; les parenthèses de la fonction font office de parenthèses du générateur (pas besoin de les dédoubler).
print(sum(n for n in range(5)))10
À partir de notes = [12, 15, 9, 17, 11, 14], produisez en une ligne pour chaque question :
{note: "admis"/"recalé"}.notes = [12, 15, 9, 17, 11, 14]
# 1. Chaînes
...
# 2. Pourcentages
...
# 3. Notes uniques
...
# 4. Dict admis/recalé
...notes = [12, 15, 9, 17, 11, 14, 12] # avec un doublon pour Q3
# 1. Chaînes
chaines = [str(n) for n in notes]
print(chaines)
# 2. Pourcentages
pourcentages = [n * 5 for n in notes]
print(pourcentages)
# 3. Notes uniques
uniques = {n for n in notes}
print(uniques)
# 4. Dict admis/recalé
statuts = {n: ("admis" if n >= 10 else "recalé") for n in notes}
print(statuts)['12', '15', '9', '17', '11', '14', '12']
[60, 75, 45, 85, 55, 70, 60]
{9, 11, 12, 14, 15, 17}
{12: 'admis', 15: 'admis', 9: 'recalé', 17: 'admis', 11: 'admis', 14: 'admis'}
Soit une liste de dictionnaires :
produits = [
{"nom": "Pain", "prix": 1.20, "bio": True},
{"nom": "Lait", "prix": 0.95, "bio": False},
{"nom": "Fromage", "prix": 4.50, "bio": True},
{"nom": "Vin", "prix": 12.0, "bio": False},
{"nom": "Miel", "prix": 7.80, "bio": True},
]En compréhensions exclusivement, produisez :
{nom: prix_TTC} avec TVA à 5,5 % (bio) ou 20 % (non bio).produits = [
{"nom": "Pain", "prix": 1.20, "bio": True},
{"nom": "Lait", "prix": 0.95, "bio": False},
{"nom": "Fromage", "prix": 4.50, "bio": True},
{"nom": "Vin", "prix": 12.0, "bio": False},
{"nom": "Miel", "prix": 7.80, "bio": True},
]
# 1. Noms
...
# 2. Noms bio
...
# 3. Bio < 10€
...
# 4. Dict avec TVA variable
...produits = [
{"nom": "Pain", "prix": 1.20, "bio": True},
{"nom": "Lait", "prix": 0.95, "bio": False},
{"nom": "Fromage", "prix": 4.50, "bio": True},
{"nom": "Vin", "prix": 12.0, "bio": False},
{"nom": "Miel", "prix": 7.80, "bio": True},
]
# 1. Noms
noms = [p["nom"] for p in produits]
print("Tous :", noms)
# 2. Noms bio
noms_bio = [p["nom"] for p in produits if p["bio"]]
print("Bio :", noms_bio)
# 3. Bio < 10€
accessibles = [p for p in produits if p["bio"] and p["prix"] < 10]
print("Bio abordables :", [p["nom"] for p in accessibles])
# 4. Dict avec TVA variable
prix_ttc = {
p["nom"]: round(p["prix"] * (1.055 if p["bio"] else 1.20), 2)
for p in produits
}
print("Prix TTC :", prix_ttc)Tous : ['Pain', 'Lait', 'Fromage', 'Vin', 'Miel']
Bio : ['Pain', 'Fromage', 'Miel']
Bio abordables : ['Pain', 'Fromage', 'Miel']
Prix TTC : {'Pain': 1.27, 'Lait': 1.14, 'Fromage': 4.75, 'Vin': 14.4, 'Miel': 8.23}
Soit la matrice 3×3 :
matrice = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]En compréhensions, produisez :
matrice = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# 1. Aplatir
...
# 2. Transposer
...
# 3. Éléments pairs
...
# 4. Somme de chaque ligne
...matrice = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# 1. Aplatir
aplatie = [x for ligne in matrice for x in ligne]
print("Aplatie :", aplatie)
# 2. Transposer : lignes ↔ colonnes
# Pour chaque colonne j, construire une liste de matrice[i][j] pour chaque i
transposee = [[matrice[i][j] for i in range(3)] for j in range(3)]
print("Transposée :", transposee)
# 3. Pairs
pairs = [x for ligne in matrice for x in ligne if x % 2 == 0]
print("Pairs :", pairs)
# 4. Somme par ligne
sommes = [sum(ligne) for ligne in matrice]
print("Sommes :", sommes)Aplatie : [1, 2, 3, 4, 5, 6, 7, 8, 9]
Transposée : [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Pairs : [2, 4, 6, 8]
Sommes : [6, 15, 24]
Python offre une façon plus élégante de transposer avec zip :
matrice = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# * déballe la matrice en arguments séparés pour zip
transposee = [list(colonne) for colonne in zip(*matrice)]
print(transposee)[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Le *matrice est un déballage (vu au chapitre 2). zip(*matrice) donne les colonnes.
À partir d’une liste de notes, sans boucle explicite, renvoyez un dict contenant nb_notes, moyenne, nb_admis, taux_reussite (en %).
def stats(notes):
...
print(stats([12, 15, 9, 17, 11, 14, 8]))def stats(notes):
if not notes:
return {"nb_notes": 0, "moyenne": 0, "nb_admis": 0, "taux_reussite": 0}
admis = sum(1 for n in notes if n >= 10)
return {
"nb_notes": len(notes),
"moyenne": round(sum(notes) / len(notes), 2),
"nb_admis": admis,
"taux_reussite": round(admis / len(notes) * 100, 1),
}
print(stats([12, 15, 9, 17, 11, 14, 8]))
print(stats([5, 4, 3]))
print(stats([])){'nb_notes': 7, 'moyenne': 12.29, 'nb_admis': 5, 'taux_reussite': 71.4}
{'nb_notes': 3, 'moyenne': 4.0, 'nb_admis': 0, 'taux_reussite': 0.0}
{'nb_notes': 0, 'moyenne': 0, 'nb_admis': 0, 'taux_reussite': 0}
sum(1 for n in notes if n >= 10) compte les notes vérifiant la condition, sans créer de liste intermédiaire.sum(n >= 10 for n in notes) — plus court, puisque True vaut 1.[expression for element in iterable if condition].[], set {} (sans :), dict {:} (avec :), générateur ().if : l’un à droite de for (filtre), l’autre dans l’expression (ternaire).for le plus à gauche est le plus externe.(...) — lazy, économes en mémoire. Idéal pour sum, any, all, max…← Retour à la présentation du niveau • Chapitre suivant : *args et **kwargs →