Aller au contenu

Chapitre 0 - Partie 2 : Mise au point des programmes et gestion des bugs


Détectées avant l'exécution par l'interpréteur Python. Le programme ne démarre pas.

# Parenthèse manquante
print("bonjour"
# SyntaxError: unexpected EOF while parsing
# Indentation incorrecte
def f():
print("test")
# IndentationError: expected an indented block

Détectées pendant l'exécution. Le programme s'arrête brutalement.

# Division par zéro
print(10 / 0)
# ZeroDivisionError
# Index hors limites
tab = [1, 2, 3]
print(tab[5])
# IndexError: list index out of range
# Type incompatible
print("age : " + 17)
# TypeError: can only concatenate str to str

Les plus difficiles à détecter : le programme s'exécute sans erreur mais produit un résultat faux.

# Bug : calcul de la moyenne
def moyenne(notes):
return sum(notes) / len(notes) + 1 # le + 1 est une erreur

# Concaténation au lieu d'addition
age = input("Âge : ") # renvoie une chaîne
print(age + 1) # TypeError !
# Correction : int(input("Âge : "))
# Confusion entier / flottant
print(7 / 2) # 3.5 (division réelle)
print(7 // 2) # 3 (division entière)

Un effet de bord se produit quand une fonction modifie quelque chose en dehors d'elle (variable globale, argument mutable).

# Bug classique : modification d'une liste passée en argument
def ajouter_element(liste, element):
liste.append(element) # modifie la liste originale !
return liste
ma_liste = [1, 2, 3]
nouvelle = ajouter_element(ma_liste, 4)
print(ma_liste) # [1, 2, 3, 4] - modifiée involontairement !
# Correction : travailler sur une copie
def ajouter_element(liste, element):
copie = liste.copy()
copie.append(element)
return copie
# Bug classique : argument par défaut mutable
def ajouter(element, liste=[]): # DANGER !
liste.append(element)
return liste
print(ajouter(1)) # [1]
print(ajouter(2)) # [1, 2] - surprise !
# Correction : utiliser None comme valeur par défaut
def ajouter(element, liste=None):
if liste is None:
liste = []
liste.append(element)
return liste
tab = [10, 20, 30]
# Bug : index hors limites
for i in range(len(tab) + 1): # range(4) → 0, 1, 2, 3
print(tab[i]) # IndexError quand i = 3
# Correction
for i in range(len(tab)): # range(3) → 0, 1, 2
print(tab[i])
# Bug : cas non traité
def categorie(age):
if age < 12:
return "enfant"
elif age < 18:
return "adolescent"
# Que se passe-t-il si age >= 18 ? → renvoie None
# Correction
def categorie(age):
if age < 12:
return "enfant"
elif age < 18:
return "adolescent"
else:
return "adulte"

2.5 Choix des inégalités (erreur « off-by-one »)

Section intitulée « 2.5 Choix des inégalités (erreur « off-by-one ») »
# Bug : < au lieu de <=
def est_dans_intervalle(x, a, b):
return a < x < b # exclut a et b
# Si on veut inclure les bornes :
def est_dans_intervalle(x, a, b):
return a <= x <= b
# Bug : boucle qui itère une fois de trop ou de moins
for i in range(1, 10): # 1, 2, ..., 9 (pas 10 !)
for i in range(1, 11): # 1, 2, ..., 10
# Bug : comparaison directe de flottants
print(0.1 + 0.2 == 0.3) # False !
print(0.1 + 0.2) # 0.30000000000000004
# Correction : comparer avec une tolérance
def presque_egal(a, b, epsilon=1e-9):
return abs(a - b) < epsilon
print(presque_egal(0.1 + 0.2, 0.3)) # True
# Bug : confusion entre variables aux noms proches
longueur = 10
largeur = 5
aire = longueur * longueur # devrait être longueur * largeur
# Bug : variable locale masquant une variable de portée supérieure
total = 100
def ajouter(n):
total = 0 # crée une variable locale, ne modifie pas la globale
total += n
return total

La méthode la plus simple : insérer des print() pour observer les valeurs des variables.

def recherche(tab, val):
for i in range(len(tab)):
print(f" i={i}, tab[i]={tab[i]}, val={val}") # debug
if tab[i] == val:
return i
return -1

Les assertions vérifient des conditions qui doivent être vraies. Si la condition est fausse, le programme s'arrête avec un message explicite.

def moyenne(notes):
assert len(notes) > 0, "La liste de notes ne doit pas être vide"
assert all(0 <= n <= 20 for n in notes), "Notes entre 0 et 20"
return sum(notes) / len(notes)
def recherche_dicho(tab, val):
"""Recherche val dans tab (trié) par dichotomie.
Args:
tab: liste triée par ordre croissant
val: valeur à rechercher
Returns:
L'indice de val si trouvé, -1 sinon.
Raises:
AssertionError: si tab n'est pas trié.
"""
assert all(tab[i] <= tab[i+1] for i in range(len(tab)-1)), "tab doit être trié"
# ...

Tester systématiquement avec des cas variés :

def est_premier(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % 2 == 0:
return False
return True
# Jeu de tests
assert est_premier(2) == True
assert est_premier(3) == True
assert est_premier(4) == False
assert est_premier(17) == True
assert est_premier(1) == False
assert est_premier(0) == False
assert est_premier(-5) == False
# Bug détecté ! est_premier(9) renvoie True au lieu de False
# Cause : n % 2 au lieu de n % i dans la boucle

Un débogueur (Thonny, VS Code, etc.) permet :

  • l'exécution pas à pas ;
  • l'inspection des variables à chaque étape ;
  • la pose de points d'arrêt (breakpoints) ;
  • la navigation dans la pile d'appels.

PratiqueDescription
Nommer clairementindice_max plutôt que x ou temp
Tester au fur et à mesureTester chaque fonction dès qu'elle est écrite
Écrire les tests d'abordDéfinir les cas de test avant de coder (TDD)
Limiter la portéeÉviter les variables globales
DocumenterDocstrings pour chaque fonction
Utiliser des assertionsVérifier les préconditions
Copier les mutablesNe pas modifier involontairement les listes passées en argument
Éviter la comparaison de flottantsUtiliser une tolérance (epsilon)

Type de bugExempleDétection
SyntaxeParenthèse manquanteAvant l'exécution
ExécutionDivision par zéro, index hors limitesPendant l'exécution (exception)
LogiqueRésultat faux, condition non exhaustivePar les tests
Typage"5" + 3TypeError à l'exécution
Effet de bordModification involontaire d'une listePar les tests
Off-by-one< au lieu de <=, range(n) vs range(n+1)Par les tests
Flottants0.1 + 0.2 != 0.3Comparaison avec epsilon