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. đȘ€