Dans le cours précédent, vous avez appris les variables, les conditions, les fonctions (récursives, et même d'ordre supérieur). Si ce n'est pas encore frais, un petit rappel :
def mention(note):
if note >= 10:
return "Validé"
else:
return "Non"
print(mention(14)) # Validé
Aujourd'hui, on ajoute deux briques fondamentales : les listes (pour stocker plusieurs valeurs) et les boucles (pour répéter des actions). Avec ça, vous pourrez manipuler des signaux, des polynômes, des vecteurs — bref, faire de vrais calculs.
Une liste (ou array à une dimension), c'est une séquence ordonnée d'éléments. Imaginez un tiroir à compartiments : chaque compartiment contient une valeur, et chaque compartiment a un numéro (son indice).
# Une liste se crée avec des crochets
notes = [12, 15, 8, 19, 10]
# On accède à un élément par son indice (qui commence à 0)
print(notes[0]) # 12 — premier élément
print(notes[2]) # 8 — troisième élément
# Indices négatifs : on compte depuis la fin
print(notes[-1]) # 10 — dernier élément
print(notes[-2]) # 19 — avant-dernier
# Nombre d'éléments
print(len(notes)) # 5
notes = [12, 15, 8, 19, 10]
notes[2] = 14 # remplacer le 8 par un 14
notes.append(16) # ajouter 16 à la fin
notes.append(7) # ajouter 7 à la fin
print(notes)
# [12, 15, 14, 19, 10, 16, 7]
append est la méthode la plus utilisée de Python. Elle ajoute un
élément à la fin de la liste en temps quasi-constant (O(1) pour les
intimes). Ne l'oubliez pas : elle sauve des vies. Ou au moins des notes de bas de page.
Le slicing permet d'extraire une sous-liste. La syntaxe :
liste[début:fin:pas].
nombres = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nombres[2:6]) # [2, 3, 4, 5] — de l'indice 2 à 5 inclus
print(nombres[:4]) # [0, 1, 2, 3] — du début à l'indice 3
print(nombres[6:]) # [6, 7, 8, 9] — de l'indice 6 à la fin
print(nombres[::2]) # [0, 2, 4, 6, 8] — un élément sur deux
print(nombres[::-1]) # [9, 8, 7, ..., 0] — la liste à l'envers !
# Une liste peut contenir n'importe quoi
fourre_tout = [42, "Python", 3.14, True, [1, 2, 3]]
print(fourre_tout[1]) # "Python"
print(fourre_tout[4][0]) # 1 — une liste dans une liste
for : répéter sans se fatiguer
La boucle for permet de parcourir chaque élément
d'une séquence (liste, chaîne, etc.) et d'exécuter du code pour chacun.
C'est le couteau suisse du programmeur.
notes = [12, 15, 8, 19, 10]
for note in notes:
print("Note :", note)
range() — générer des séquences numériques
range(n) génère les entiers de 0 à n-1. Utile quand on veut répéter
une action un nombre précis de fois, ou quand on a besoin des indices.
# Afficher les nombres de 0 à 4
for i in range(5):
print(i, "au carré =", i**2)
Variantes de range :
range(3, 8) # 3, 4, 5, 6, 7 (début, fin — fin exclue)
range(0, 10, 2) # 0, 2, 4, 6, 8 (début, fin, pas)
range(5, 0, -1) # 5, 4, 3, 2, 1 (pas négatif = on descend)
range est un générateur paresseux (lazy) :
il ne calcule pas tous les nombres d'un coup, il les produit un par un quand
on les demande. C'est économe en mémoire. Si vous faites
range(10**12), vous ne créez PAS une liste de mille milliards
d'entiers — vous créez un objet qui sait les produire à la demande. Malin.
enumerate
Parfois, on a besoin à la fois de l'indice et de la valeur.
enumerate fait les deux :
couleurs = ["rouge", "vert", "bleu"]
for i, couleur in enumerate(couleurs):
print("Couleur n°", i+1, ":", couleur)
Les compréhensions de listes sont une façon concise de créer une nouvelle liste à partir d'une autre. C'est une spécialité Python : élégant, lisible, efficace.
nombres = [1, 2, 3, 4, 5]
# Version longue (for classique)
carrés_long = []
for n in nombres:
carrés_long.append(n ** 2)
# Version courte (compréhension)
carrés_court = [n ** 2 for n in nombres]
print(carrés_court) # [1, 4, 9, 16, 25]
# Avec un filtre (if)
pairs = [n for n in nombres if n % 2 == 0]
print(pairs) # [2, 4]
[x^2 | x <- [1..5]]. La version Python remplace les barres verticales
et les flèches par des mots-clés en anglais — plus accessible, moins intimidant.
Un bel exemple d'emprunt entre langages.
while : répéter... jusqu'à ce que
Si for sert à parcourir une séquence connue, while sert à
répéter tant qu'une condition est vraie. On ne sait pas à l'avance combien
d'itérations seront nécessaires — on s'arrête quand la condition devient fausse.
for, c'est le métro : vous savez combien de stations.
while, c'est la route : vous roulez jusqu'à arriver.
Et si vous oubliez la condition d'arrêt, vous faites le tour de la Terre
indéfiniment. (On appelle ça une boucle infinie.
Ça arrive aux meilleurs. Ctrl+C pour vous échapper.)
# Tant qu'il reste du café, on boit
tasse = 5
while tasse > 0:
print("☕ Il reste", tasse, "tasses. On boit.")
tasse = tasse - 1
print("Plus de café. Allons dormir.")
tasse = tasse - 1
ci-dessus), la boucle tourne à l'infini. Python ne vous arrêtera pas.
Votre ordinateur non plus. Les boucles infinies, c'est comme les cours de
maths à 8h : ça n'en finit pas.
break et continue — les interrupteursbreak — sort immédiatement de la boucle.continue — passe immédiatement à l'itération suivante.# break : on cherche le premier nombre divisible par 7 après 50
n = 50
while True:
if n % 7 == 0:
print("Trouvé :", n)
break # on sort
n = n + 1
# continue : afficher tous les nombres de 1 à 10 sauf les multiples de 3
for i in range(1, 11):
if i % 3 == 0:
continue # on saute ce tour
print(i, end=" ")
# Sortie : 1 2 4 5 7 8 10
break et
continue. Un break de temps en temps, ça va.
Mais si votre code ressemble à un jeu de Tetris avec des break
partout, c'est probablement que votre condition de boucle est mal conçue.
Les programmeurs débutants abusent de break. Les experts structurent
leurs boucles pour ne pas en avoir besoin.
Assez de théorie. Voici des exemples concrets d'utilisation des listes et des boucles dans des contextes mathématiques et scientifiques.
Un polynôme P(x) = a₀ + a₁x + a₂x² + ... + aₙxⁿ peut être représenté par
une liste de coefficients [a₀, a₁, a₂, ..., aₙ].
L'indice dans la liste correspond au degré du terme.
def evalue(coeffs, x):
"""Évalue le polynôme représenté par coeffs au point x."""
resultat = 0
for i, a in enumerate(coeffs):
resultat = resultat + a * (x ** i)
return resultat
# P(x) = 1 + 2x + 3x² (coefficients [1, 2, 3])
p = [1, 2, 3]
print(evalue(p, 0)) # 1
print(evalue(p, 2)) # 1 + 4 + 12 = 17
print(evalue(p, 10)) # 1 + 20 + 300 = 321
Au lieu de calculer xi à chaque itération (ce qui est coûteux), on peut utiliser la méthode de Horner : on factorise le polynôme pour n'utiliser que des multiplications et des additions. Pour P(x) = 1 + 2x + 3x², on écrit P(x) = 1 + x(2 + 3x).
def evalue_horner(coeffs, x):
resultat = 0
for a in reversed(coeffs):
resultat = resultat * x + a
return resultat
print(evalue_horner(p, 2)) # 17
Pourquoi c'est mieux ? Moins d'opérations, moins d'erreurs d'arrondi. La méthode de Horner date de 1819 mais était déjà connue des mathématiciens chinois au XIIIe siècle (Qin Jiushao). Rien de nouveau sous le soleil algorithmique.
La dérivée de P(x) = a₀ + a₁x + a₂x² + a₃x³ est P'(x) = a₁ + 2a₂x + 3a₃x². En termes de listes : on décale et on multiplie.
def derive_polynome(coeffs):
"""Retourne la liste des coefficients du polynôme dérivé."""
return [i * a for i, a in enumerate(coeffs) if i > 0]
# P(x) = 1 + 2x + 3x² + 4x³
p = [1, 2, 3, 4]
p_prime = derive_polynome(p)
print(p_prime) # [2, 6, 12] → P'(x) = 2 + 6x + 12x²
# Vérifions : P'(1) = 2 + 6 + 12 = 20
print(evalue(p_prime, 1)) # 20
Et si on calculait l'intégrale ? L'intégrale de P(x) est ∫P(x) dx = C + a₀x + (a₁/2)x² + (a₂/3)x³ + ... En code : on insère un coefficient 0 au début (la constante) et on divise chaque terme par son nouveau degré.
def integre_polynome(coeffs, constante=0):
return [constante] + [a / (i + 1) for i, a in enumerate(coeffs)]
# P(x) = 2 + 6x + 12x² → ∫P = 0 + 2x + 3x² + 4x³
print(integre_polynome([2, 6, 12])) # [0, 2.0, 3.0, 4.0]
Dériver puis intégrer (ou l'inverse) vous redonne le polynôme d'origine. Vérifiez !
En IA et en physique, un vecteur est une liste de nombres (ses composantes). Python permet de faire des calculs vectoriels avec des listes et des boucles.
def somme_vecteurs(u, v):
"""Additionne deux vecteurs composante par composante."""
return [u[i] + v[i] for i in range(len(u))]
u = [1, 2, 3]
v = [4, 5, 6]
print(somme_vecteurs(u, v)) # [5, 7, 9]
u + v ne fait PAS l'addition
composante par composante en Python ! Il concatène les listes :
[1, 2, 3, 4, 5, 6]. C'est une source d'erreur classique.
N'écrivez jamais u + v pour additionner des vecteurs.
Utilisez votre fonction ou, mieux, NumPy (bibliothèque qu'on
verra plus tard).
def produit_scalaire(u, v):
"""Calcule le produit scalaire de u et v : Σ u[i] × v[i]"""
total = 0
for i in range(len(u)):
total = total + u[i] * v[i]
return total
u = [1, 2, 3]
v = [4, 5, 6]
print(produit_scalaire(u, v)) # 1×4 + 2×5 + 3×6 = 4 + 10 + 18 = 32
import math
def norme(v):
"""Retourne la norme euclidienne ‖v‖ = √(Σ v[i]²)"""
somme = 0
for composante in v:
somme = somme + composante ** 2
return math.sqrt(somme)
v = [3, 4]
print(norme(v)) # 5.0 (triangle 3-4-5, vous vous souvenez ?)
Un signal (audio, bourse, température) peut être représenté par une liste de valeurs échantillonnées dans le temps. On applique des transformations dessus.
def normalise(signal):
"""Met le signal à l'échelle [0, 1]."""
min_val = min(signal)
max_val = max(signal)
if max_val == min_val:
return [0] * len(signal) # signal constant → tout à 0
return [(v - min_val) / (max_val - min_val) for v in signal]
signal = [2, 5, 3, 8, 1]
print(normalise(signal))
# [(2-1)/(7), (5-1)/7, (3-1)/7, (8-1)/7, (1-1)/7]
# ≈ [0.143, 0.571, 0.286, 1.0, 0.0]
def moyenne_mobile(signal, fenetre):
"""Lisse un signal par moyenne mobile sur 'fenetre' points."""
resultat = []
for i in range(len(signal) - fenetre + 1):
morceau = signal[i:i + fenetre]
moyenne = sum(morceau) / fenetre
resultat.append(moyenne)
return resultat
signal = [1, 3, 2, 8, 5, 7, 6, 4]
print(moyenne_mobile(signal, 3))
# [2.0, 4.33, 5.0, 6.67, 6.0, 5.67]
La normalisation [0, 1] n'est pas la seule méthode. On peut aussi centrer-réduire (soustraction de la moyenne, division par l'écart-type). C'est ce qu'on utilise en statistiques et en IA quand on veut que les données aient une moyenne nulle et une variance de 1.
def centre_reduit(signal):
mu = sum(signal) / len(signal)
variance = sum((x - mu) ** 2 for x in signal) / len(signal)
ecart_type = math.sqrt(variance)
if ecart_type == 0:
return [0] * len(signal)
return [(x - mu) / ecart_type for x in signal]
signal = [2, 4, 6, 8]
print(centre_reduit(signal))
# moyenne=5, variance=5, écart-type≈2.236
# [-1.34, -0.45, 0.45, 1.34]
Une opération courante : compter combien de fois chaque valeur apparaît dans une liste. Le résultat est un tableau de fréquences (ou histogramme). On crée une nouvelle liste, initialisée à zéro, qu'on remplit au fur et à mesure.
def frequences(notes, note_max=20):
"""Calcule le nombre d'étudiants ayant obtenu chaque note de 0 à note_max."""
# On crée un tableau de (note_max+1) zéros
histo = [0] * (note_max + 1)
for note in notes:
histo[note] = histo[note] + 1
return histo
notes = [12, 15, 8, 12, 15, 19, 12, 10, 15, 8]
histo = frequences(notes)
# histo[8] = 2, histo[10] = 1, histo[12] = 3, histo[15] = 3, histo[19] = 1
for note, effectif in enumerate(histo):
if effectif > 0:
print("Note", note, ":", "■" * effectif, "(" + str(effectif) + ")")
matplotlib et c'est plus joli. Mais le principe est exactement
le même : compter les occurrences. Vous venez de réinventer la table de
fréquences. Les statisticiens pleurent de joie.
def frequences_relatives(notes, note_max=20):
histo = frequences(notes, note_max)
total = len(notes)
return [effectif / total for effectif in histo]
rel = frequences_relatives(notes)
print(rel[12]) # 0.3 (30% des étudiants ont eu 12)
On peut utiliser les fréquences pour repérer des notes impossibles (ex: 25/20) :
def filtrer_notes(notes, note_max=20):
return [note for note in notes if 0 <= note <= note_max]
notes_sales = [12, 15, -3, 8, 25, 10]
print(filtrer_notes(notes_sales)) # [12, 15, 8, 10] — les deux intrus ont été virés
Propre, non ? Les compréhensions de listes avec if sont parfaites
pour le filtrage.
Python a aussi les tuples (1, 2, 3) :
comme les listes mais immuables (on ne peut pas les modifier).
On les utilise pour des données qui ne doivent pas changer (coordonnées GPS,
constantes). Les listes sont pour les données qu'on manipule.
for avec else ?
Saviez-vous qu'une boucle for peut avoir une clause
else ? Elle s'exécute uniquement si la boucle
n'a pas été interrompue par break. C'est utile
pour les recherches : si on trouve l'élément, break;
sinon, on exécute le else. Contre-intuitif mais pratique.
Quand on écrit b = a (où a est une liste),
b n'est pas une copie : c'est une nouvelle
référence vers la même liste. Modifier b modifie aussi
a. Pour copier : b = a[:] ou
b = a.copy(). Ne l'oubliez pas, ou vous passerez 3 heures
à chercher un bug. (Je parle d'expérience.)
a = [1, 2, 3]
b = a # b pointe vers la même liste que a
b[0] = 999
print(a[0]) # 999 — surprise ! a aussi changé
c = a[:] # vraie copie
c[0] = 42
print(a[0]) # 999 — a n'a pas changé, ouf
Vous avez survolé ce qui fait la puissance des langages de programmation moderne : les listes pour structurer les données, les boucles pour les traiter, et tout un tas d'applications (polynômes, vecteurs, signaux). Félicitations.
for. Pas de listes. Pas de programme
stocké en mémoire. Chaque fois que vous écrivez for x in liste,
vous marchez sur les pas d'un génie.
for et while. 🔄a = b sur des listes est un piège. 🪤