23  Itération avancée et range

Vous savez déjà écrire des boucles for et while. Ce chapitre va plus loin : comment itérer proprement sur les structures complexes (dicts, listes imbriquées), et comment enchaîner les opérations de filtrage / agrégation / transformation — les briques de base du traitement de données en Python.

23.1 Rappel : les objets itérables

Un objet est itérable si on peut le parcourir avec for. En Python, c’est le cas de :

  • Listes, tuples, sets, dictionnaires.
  • Chaînes de caractères.
  • Objets range.
  • Fichiers ouverts (chaque ligne devient un élément).
  • Générateurs (vus en Partie 4).
for x in [1, 2, 3]:        print(x, end=" ")
print()
for c in "abc":            print(c, end=" ")
print()
for k in {"a": 1, "b": 2}: print(k, end=" ")
print()
for n in range(3):         print(n, end=" ")
1 2 3 
a b c 
a b 
0 1 2 
Test : un objet est-il itérable ?

Un objet est itérable s’il a une méthode spéciale __iter__. On peut tester avec hasattr :

print(hasattr([1, 2], "__iter__"))        # True
print(hasattr("abc", "__iter__"))         # True
print(hasattr(42, "__iter__"))            # False
True
True
False

On approfondira le protocole d’itération en Partie 4.

23.2 Parcourir un dictionnaire : les 3 méthodes à connaître

Vu au chapitre 7 de la Partie 1, mais essentielles à maîtriser. Le TOSA teste les trois façons.

Parcours des clés (par défaut)

personne = {"nom": "Alice", "age": 30, "ville": "Paris"}

for cle in personne:              # équivaut à personne.keys()
    print(cle)
nom
age
ville

Parcours des valeurs

for valeur in personne.values():
    print(valeur)
Alice
30
Paris

Parcours simultané clé + valeur (items)

La forme la plus utile, à privilégier systématiquement quand vous avez besoin des deux :

for cle, valeur in personne.items():
    print(f"{cle}{valeur}")
nom → Alice
age → 30
ville → Paris

Tableau récapitulatif

Ce qu’on veut Méthode
Juste les clés for k in d:
Juste les valeurs for v in d.values():
Les paires clé/valeur for k, v in d.items():

23.3 Filtrer pendant un parcours

Filtrer = garder certains éléments selon une condition.

notes = [12, 8, 15, 7, 18, 10, 14]

# Ne garder que les notes >= 10
reussites = []
for n in notes:
    if n >= 10:
        reussites.append(n)

print(reussites)
[12, 15, 18, 10, 14]

23.4 Agréger pendant un parcours

Agréger = combiner plusieurs valeurs en une seule (somme, moyenne, maximum, comptage…).

notes = [12, 8, 15, 7, 18, 10, 14]

# Somme, moyenne
somme = 0
for n in notes:
    somme += n
print("Somme :", somme)
print("Moyenne :", somme / len(notes))
Somme : 84
Moyenne : 12.0
# Comptage sous condition
notes = [12, 8, 15, 7, 18, 10, 14]

nb_reussites = 0
for n in notes:
    if n >= 10:
        nb_reussites += 1

print(f"{nb_reussites} élèves ont la moyenne sur {len(notes)}.")
5 élèves ont la moyenne sur 7.
Raccourci avec les fonctions natives

Pour des agrégations simples, Python propose des raccourcis :

notes = [12, 8, 15, 7, 18, 10, 14]

print("Somme    :", sum(notes))
print("Moyenne  :", sum(notes) / len(notes))
print("Max      :", max(notes))
print("Min      :", min(notes))
Somme    : 84
Moyenne  : 12.0
Max      : 18
Min      : 7

23.5 Transformer pendant un parcours

Transformer = appliquer une fonction à chaque élément pour produire une nouvelle collection.

notes_sur_20 = [12, 15, 9, 17, 11]

# Convertir en note sur 100
notes_sur_100 = []
for n in notes_sur_20:
    notes_sur_100.append(n * 5)

print(notes_sur_100)
[60, 75, 45, 85, 55]
# Mettre tous les prénoms en majuscules
prenoms = ["alice", "bob", "charlie"]

majuscules = []
for p in prenoms:
    majuscules.append(p.upper())

print(majuscules)
['ALICE', 'BOB', 'CHARLIE']
Une manière plus Pythonique existe

Les 3 opérations (filtrer / agréger / transformer) s’écrivent bien plus élégamment avec les compréhensions de liste — vues au niveau Avancé (Partie 3).

# Filtrer
reussites = [n for n in notes if n >= 10]

# Transformer
notes_sur_100 = [n * 5 for n in notes_sur_20]

# Combiner
reussites_sur_100 = [n * 5 for n in notes if n >= 10]

Pour l’instant, le for explicite suffit.

23.6 Itération imbriquée

Une boucle dans une autre boucle. Le chapitre 9 de la Partie 1 l’a vu ; rappel pour des usages spécifiques :

Parcourir une liste de listes

matrice = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]

for ligne in matrice:
    for valeur in ligne:
        print(valeur, end=" ")
    print()
1 2 3 
4 5 6 
7 8 9 

Parcourir un dict de listes

notes_classe = {
    "Alice":   [12, 15, 14],
    "Bob":     [11, 10, 9],
    "Charlie": [18, 17, 19],
}

for prenom, notes in notes_classe.items():
    moyenne = sum(notes) / len(notes)
    print(f"{prenom} : moyenne = {moyenne:.1f}")
Alice : moyenne = 13.7
Bob : moyenne = 10.0
Charlie : moyenne = 18.0

Produit cartésien

Parcourir toutes les combinaisons de deux séquences :

couleurs = ["rouge", "vert", "bleu"]
tailles = ["S", "M", "L"]

for c in couleurs:
    for t in tailles:
        print(f"{c}-{t}", end="  ")
rouge-S  rouge-M  rouge-L  vert-S  vert-M  vert-L  bleu-S  bleu-M  bleu-L  

23.7 L’opérateur in et not in

Déjà rencontré. Rappels des cas subtils :

# Sur une liste → cherche dans les éléments
print(3 in [1, 2, 3])              # True

# Sur une chaîne → cherche une sous-chaîne
print("ana" in "banane")           # True

# Sur un dict → cherche dans les CLÉS
d = {"nom": "Alice", "age": 30}
print("nom" in d)                  # True
print("Alice" in d)                # False ! (valeur, pas clé)
print("Alice" in d.values())       # True
True
True
True
False
True
Performance : in n’est pas égal partout
Structure Complexité de in
list / tuple O(n) — parcours linéaire
str O(n × m)
set / frozenset O(1) — quasi-instantané
dict (sur les clés) O(1)

Pour des tests d’appartenance nombreux, convertir en set avant peut accélérer drastiquement.

23.8 Méthodes structurées importantes à connaître

enumerate() — boucle avec l’indice

Revu en Partie 1. On insiste : c’est LA façon Pythonique d’itérer avec l’indice.

fruits = ["pomme", "banane", "cerise"]

# ❌ Non-Pythonique
for i in range(len(fruits)):
    print(i, fruits[i])

print()

# ✅ Pythonique
for i, fruit in enumerate(fruits):
    print(i, fruit)

# Avec un démarrage personnalisé
print()
for rang, fruit in enumerate(fruits, start=1):
    print(f"{rang}. {fruit}")
0 pomme
1 banane
2 cerise

0 pomme
1 banane
2 cerise

1. pomme
2. banane
3. cerise

zip() — parcours parallèle

Pour parcourir plusieurs séquences en parallèle.

prenoms = ["Alice", "Bob", "Charlie"]
notes = [14, 12, 17]
mentions = ["Bien", "Passable", "Très bien"]

for p, n, m in zip(prenoms, notes, mentions):
    print(f"{p} ({n}) : {m}")
Alice (14) : Bien
Bob (12) : Passable
Charlie (17) : Très bien

Depuis Python 3.10, on peut ajouter strict=True pour lever une erreur si les longueurs diffèrent :

list(zip([1, 2, 3], ["a", "b"], strict=True))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[18], line 1
----> 1 list(zip([1, 2, 3], ["a", "b"], strict=True))

ValueError: zip() argument 2 is shorter than argument 1

reversed() et sorted()

Revus en Partie 1. Rappels dans le contexte d’itération :

# Parcours à l'envers sans modifier
notes = [12, 15, 9, 17]
for n in reversed(notes):
    print(n, end=" ")
print()

# Parcours trié sans modifier
for n in sorted(notes):
    print(n, end=" ")
print()

# Trié par valeur pour un dict
scores = {"Alice": 14, "Bob": 17, "Charlie": 11}
for nom, score in sorted(scores.items(), key=lambda t: t[1], reverse=True):
    print(f"{nom} : {score}")
17 9 15 12 
9 12 15 17 
Bob : 17
Alice : 14
Charlie : 11

23.9 range() approfondi

Les trois formes

Forme Génère
range(n) 0, 1, ..., n-1
range(a, b) a, a+1, ..., b-1
range(a, b, pas) a, a+pas, ..., < b
print(list(range(5)))              # [0, 1, 2, 3, 4]
print(list(range(2, 8)))           # [2, 3, 4, 5, 6, 7]
print(list(range(0, 20, 3)))       # [0, 3, 6, 9, 12, 15, 18]
[0, 1, 2, 3, 4]
[2, 3, 4, 5, 6, 7]
[0, 3, 6, 9, 12, 15, 18]

Pas négatif : parcours à rebours

print(list(range(10, 0, -1)))      # [10, 9, 8, ..., 1]
print(list(range(10, 0, -2)))      # [10, 8, 6, 4, 2]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[10, 8, 6, 4, 2]

Caractéristiques à connaître

range est un itérateur paresseux

range(1_000_000_000) ne crée pas un milliard d’entiers en mémoire ! L’objet range calcule les valeurs à la demande pendant l’itération.

import sys

# Un range d'un milliard : 48 octets en mémoire !
gros_range = range(1_000_000_000)
print("Taille d'un gros range :", sys.getsizeof(gros_range), "octets")

# Une liste d'un million : 8 Mo
petite_liste = list(range(1_000_000))
print("Taille d'une liste de 1M :", sys.getsizeof(petite_liste), "octets")
Taille d'un gros range : 48 octets
Taille d'une liste de 1M : 8000056 octets

C’est un avantage majeur : vous pouvez boucler sur d’énormes plages sans saturer la mémoire.

Cas d’usage typiques

Construire un indice :

fruits = ["pomme", "banane", "cerise"]
for i in range(len(fruits)):
    print(i, fruits[i])      # Préférez enumerate() en pratique
0 pomme
1 banane
2 cerise

Générer une séquence numérique :

carres = []
for i in range(1, 11):
    carres.append(i ** 2)
print(carres)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Itérer un nombre de fois sans utiliser la variable :

# Convention : _ quand la variable ne sert pas
for _ in range(3):
    print("Boum !")
Boum !
Boum !
Boum !

23.10 Filtrer + transformer + agréger : combinaison

Un exemple concret qui combine les trois :

notes = [
    ("Alice",   14),
    ("Bob",      8),
    ("Charlie", 17),
    ("Diana",    9),
    ("Eve",     15),
]

# Problème : moyenne des notes des élèves ayant au moins 10
# Filtrer puis agréger
notes_valides = []
for nom, note in notes:
    if note >= 10:
        notes_valides.append(note)

if notes_valides:
    print(f"Moyenne des élèves réussis : {sum(notes_valides) / len(notes_valides):.2f}")
else:
    print("Aucun élève n'a la moyenne")
Moyenne des élèves réussis : 15.33

🧩 Quiz 4.1 — Itération avancée

Question 1

Quelle est la forme idiomatique pour parcourir un dict avec clés et valeurs ?

  1. for k in d: print(k, d[k])
  2. for k, v in d.items(): print(k, v)
  3. for k in d.keys(): print(k, d.get(k))
  4. for k, v in d: print(k, v)

b) for k, v in d.items(): — la forme idiomatique Python. L’option a) fonctionne mais cherche deux fois la clé dans le dict (moins efficace). L’option d) ne fonctionne pas : parcourir un dict sans méthode donne les clés, pas des paires.

d = {"a": 1, "b": 2}

# Variante inefficace (mais fonctionne)
for k in d:
    print(k, d[k])

print()

# Forme idiomatique
for k, v in d.items():
    print(k, v)
a 1
b 2

a 1
b 2

Question 2

Que renvoie "Alice" in {"nom": "Alice"} ?

  1. True
  2. False
  3. Une erreur
  4. None

b) Falsein teste les clés d’un dict. Ici "Alice" est une valeur. Pour tester une valeur, il faut in d.values().

d = {"nom": "Alice"}
print("Alice" in d)
print("Alice" in d.values())
print("nom" in d)
False
True
True

Question 3

Que renvoie ce code ?

r = range(2, 10, 3)
print(list(r))
  1. [2, 5, 8]
  2. [2, 3, 4, 5, 6, 7, 8, 9]
  3. [2, 5, 8, 11]
  4. [3, 6, 9]

a) [2, 5, 8] — départ 2, pas 3, arrêt avant 10. Donc 2, 5, 8 (11 serait hors bornes).

print(list(range(2, 10, 3)))
[2, 5, 8]

Question 4

Que fait for _ in range(5): ?

  1. Une erreur (underscore interdit)
  2. Boucle 5 fois sans utiliser la variable d’itération
  3. Boucle infinie
  4. Boucle 4 fois

b) Boucle 5 fois sans utiliser la variable — convention Python : _ signale qu’on ignore la variable.

for _ in range(5):
    print("Hello", end=" ")
Hello Hello Hello Hello Hello 

Question 5

Combien d’itérations effectue cette boucle ?

for i in range(10, 0, -2):
    pass
  1. 4
  2. 5
  3. 6
  4. 10

b) 5 — les valeurs sont 10, 8, 6, 4, 2. La boucle s’arrête avant 0.

print(list(range(10, 0, -2)))
print(len(list(range(10, 0, -2))))
[10, 8, 6, 4, 2]
5

Question 6

Que s’affiche-t-il ?

for i, c in enumerate("abc", start=10):
    print(i, c)
  1. 0 a / 1 b / 2 c
  2. 10 a / 11 b / 12 c
  3. a 10 / b 11 / c 12
  4. Une erreur

b) 10 a / 11 b / 12 cstart=10 fait démarrer l’indice à 10.

for i, c in enumerate("abc", start=10):
    print(i, c)
10 a
11 b
12 c

Question 7

Quelle structure est la plus rapide pour tester x in ... ?

  1. list
  2. tuple
  3. str
  4. set

d) set — complexité O(1) (hash table). Les listes et tuples sont en O(n), les chaînes en O(n×m).

# Démonstration (à très grande échelle)
import time

grande_liste = list(range(1_000_000))
grand_set = set(grande_liste)

t0 = time.time()
for _ in range(1000):
    _ = 500_000 in grande_liste
print(f"list : {(time.time() - t0)*1000:.1f} ms")

t0 = time.time()
for _ in range(1000):
    _ = 500_000 in grand_set
print(f"set  : {(time.time() - t0)*1000:.1f} ms")
list : 2855.2 ms
set  : 0.0 ms

✏️ Exercice 4.1 — Moyennes par matière

Soit un dictionnaire associant chaque matière à une liste de notes :

notes = {
    "Maths":    [12, 15, 9, 17],
    "Physique": [11, 10, 13, 14],
    "Français": [16, 18, 15, 17, 14],
}

Affichez la moyenne de chaque matière, arrondie à 1 décimale.

notes = {
    "Maths":    [12, 15, 9, 17],
    "Physique": [11, 10, 13, 14],
    "Français": [16, 18, 15, 17, 14],
}

# Pour chaque matière, calculer et afficher la moyenne
notes = {
    "Maths":    [12, 15, 9, 17],
    "Physique": [11, 10, 13, 14],
    "Français": [16, 18, 15, 17, 14],
}

for matiere, liste_notes in notes.items():
    moyenne = sum(liste_notes) / len(liste_notes)
    print(f"{matiere:<10} : {moyenne:.1f}")
Maths      : 13.2
Physique   : 12.0
Français   : 16.0

✏️ Exercice 4.2 — Filtrer et compter

À partir d’une liste de tuples (prenom, age), comptez le nombre de majeurs et affichez leurs prénoms.

personnes = [
    ("Alice", 17),
    ("Bob", 25),
    ("Charlie", 15),
    ("Diana", 30),
    ("Eve", 19),
]
personnes = [
    ("Alice", 17),
    ("Bob", 25),
    ("Charlie", 15),
    ("Diana", 30),
    ("Eve", 19),
]

# Compter et afficher les majeurs
personnes = [
    ("Alice", 17),
    ("Bob", 25),
    ("Charlie", 15),
    ("Diana", 30),
    ("Eve", 19),
]

majeurs = []
for prenom, age in personnes:
    if age >= 18:
        majeurs.append(prenom)

print(f"{len(majeurs)} majeur(s) :")
for nom in majeurs:
    print(f"  - {nom}")
3 majeur(s) :
  - Bob
  - Diana
  - Eve

✏️ Exercice 4.3 — Produit cartésien

Générez tous les jetons d’un jeu de cartes classique (52 cartes) : produit cartésien des 4 couleurs et des 13 valeurs.

Format attendu : "As de Cœur", "2 de Pique", …, "Roi de Trèfle".

couleurs = ["Cœur", "Carreau", "Pique", "Trèfle"]
valeurs = ["As", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Valet", "Dame", "Roi"]

# Générer toutes les combinaisons
cartes = []
...

print(f"Total : {len(cartes)} cartes")
print("Premières :", cartes[:3])
print("Dernières :", cartes[-3:])
couleurs = ["Cœur", "Carreau", "Pique", "Trèfle"]
valeurs = ["As", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Valet", "Dame", "Roi"]

cartes = []
for couleur in couleurs:
    for valeur in valeurs:
        cartes.append(f"{valeur} de {couleur}")

print(f"Total : {len(cartes)} cartes")
print("Premières :", cartes[:3])
print("Dernières :", cartes[-3:])
Total : 52 cartes
Premières : ['As de Cœur', '2 de Cœur', '3 de Cœur']
Dernières : ['Valet de Trèfle', 'Dame de Trèfle', 'Roi de Trèfle']

✏️ Exercice 4.4 — Boucle avec zip

Soit trois listes parallèles représentant les données de capteurs :

timestamps = ["09:00", "10:00", "11:00", "12:00"]
temperatures = [12.5, 14.0, 16.3, 18.1]
humidites = [65, 62, 58, 55]

Produisez un affichage synthétique aligné, avec une ligne par mesure.

timestamps = ["09:00", "10:00", "11:00", "12:00"]
temperatures = [12.5, 14.0, 16.3, 18.1]
humidites = [65, 62, 58, 55]

# Afficher un tableau aligné
timestamps = ["09:00", "10:00", "11:00", "12:00"]
temperatures = [12.5, 14.0, 16.3, 18.1]
humidites = [65, 62, 58, 55]

print(f"{'Heure':<8}{'Temp. (°C)':>12}{'Humidité (%)':>14}")
print("-" * 34)
for h, t, hum in zip(timestamps, temperatures, humidites):
    print(f"{h:<8}{t:>12.1f}{hum:>14}")
Heure     Temp. (°C)  Humidité (%)
----------------------------------
09:00           12.5            65
10:00           14.0            62
11:00           16.3            58
12:00           18.1            55

À retenir

Points clés du chapitre
  1. Trois façons de parcourir un dict : for k in d, for v in d.values(), for k, v in d.items().
  2. Filtrer / agréger / transformer : les trois opérations clés sur les collections. S’écrivent en boucle, ou plus élégamment en compréhensions (Partie 3).
  3. in sur un set est en O(1). Sur une list, O(n). Énorme différence sur de gros volumes.
  4. enumerate au lieu de range(len(...)). zip pour plusieurs séquences en parallèle.
  5. range est paresseux : consomme peu de mémoire même sur des milliards d’éléments.
  6. _ comme nom de variable signale qu’on ignore sa valeur.

← Chapitre précédent : Les f-stringsChapitre suivant : Références et mutabilité →