Exercices : mise au point et débogage

Exercice 1 : QCM

Pour chaque question, une seule réponse est correcte.

1. Quelle instruction Python permet de vérifier une condition et d’interrompre le programme si elle est fausse ?

  • A. try condition
  • B. raise condition
  • C. assert condition
  • D. if not condition: exit()

Correction publiée après la séance.

2. Pourquoi la condition while lst[i] < 100 and i < len(lst) est-elle dangereuse ?

  • A. len(lst) recalcule la longueur à chaque itération
  • B. La condition devrait utiliser or au lieu de and
  • C. Il manque une instruction break
  • D. Python accède à lst[i] avant de vérifier que i est un indice valide

Correction publiée après la séance.

3. Que vaut 0.1 + 0.2 == 0.3 en Python ?

  • A. True
  • B. False
  • C. None
  • D. Une erreur est levée

Correction publiée après la séance.

4. Quel est le résultat de ce code ?

x = [1, 2, 3]
y = x
y.append(4)
print(len(x))
  • A. 3
  • B. 4
  • C. Une erreur AttributeError
  • D. Une erreur TypeError

Correction publiée après la séance.


Exercice 2 : identifier les erreurs

Pour chaque extrait de code, identifiez le type d’erreur que Python va lever et expliquez pourquoi.

a)

def saluer(nom)
    print(f"Bonjour {nom}")
Indice

Comparer cette ligne def à celles des fonctions habituelles : que manque-t-il à sa fin ?

b)

notes = [15, 12, 18]
print(notes[3])
Indice

La liste contient trois éléments : quels sont leurs indices ?

c)

age = input("Votre âge : ")
annee_naissance = 2026 - age
Indice

Quel est le type de la valeur renvoyée par input ? Peut-on soustraire une chaîne d’un entier ?

d)

def diviser(a, b):
    return a / b

print(diviser(10, 0))
Indice

Que vaut le second nombre de la division a / b lors de cet appel ?

e)

dico = {"a": 1, "b": 2}
print(dico["c"])
Indice

Quelles sont les clés du dictionnaire ?

Correction publiée après la séance.


Exercice 3 : tracer l’exécution avec print (exercice guidé)

La fonction suivante est censée renvoyer l’indice du maximum d’une liste, et l’on veut vérifier son comportement sur des cas limites.

def indice_max(L):
    i_max = 0
    for i in range(len(L)):
        if L[i] > L[i_max]:
            i_max = i
    return i_max

a) Testez indice_max([3, 7, 2, 9, 1]). Quel résultat obtenez-vous ? Est-il correct ?

Indice

Repérer à la main la position du plus grand élément, en comptant les indices à partir de 0, et la comparer au résultat.

b) Testez indice_max([5, 5, 5]). Quel résultat obtenez-vous ? Est-ce le comportement attendu ?

Indice

Avec des éléments tous égaux, la comparaison L[i] > L[i_max] est-elle vraie une seule fois ? Quel indice reste alors dans i_max ?

c) Ajoutez des print dans la boucle pour tracer les valeurs de i, L[i], i_max et L[i_max] à chaque itération. Exécutez sur [3, 7, 2, 9, 1] et copiez la trace.

Indice

Placer un print au début du corps de la boucle, avec une f-string qui affiche les quatre valeurs.

Code à copier puis compléter
def indice_max(L):
    i_max = 0
    for i in range(len(L)):
        print(f"i = {i}, L[i] = {...}, i_max = {i_max}, L[i_max] = {...}")
        if L[i] > L[i_max]:
            i_max = i
    return i_max

indice_max([3, 7, 2, 9, 1])

d) Testez maintenant indice_max([9, 1, 9, 1]). Quel indice est renvoyé ? Si l’on souhaite renvoyer le dernier indice du maximum (et non le premier), quelle modification faut-il apporter ?

Indice

Lorsque L[i] est égal au maximum déjà trouvé, i_max change-t-il ? Quelle comparaison le ferait changer ?

Correction publiée après la séance.


Exercice 4 : les pièges des flottants

a) Exécutez les expressions suivantes en Python et notez les résultats :

0.1 + 0.2
0.1 + 0.2 == 0.3
1.0 + 1e-16 == 1.0

Expliquez chaque résultat.

Indice

0,1 et 0,2 n’ont pas d’écriture binaire finie : ils sont stockés arrondis. Quant à 1e-16, il est trop petit devant 1 pour modifier le flottant 1.0, dont la précision est d’environ 16 chiffres significatifs.

b) La fonction suivante est censée vérifier si un triangle est rectangle en utilisant le théorème de Pythagore. Pourquoi peut-elle échouer avec certaines valeurs ?

def est_rectangle(a, b, c):
    """Vérifie si un triangle de côtés a, b, c est rectangle."""
    cotes = sorted([a, b, c])
    return cotes[0]**2 + cotes[1]**2 == cotes[2]**2
Indice

Avec des côtés à virgule, les carrés sont-ils calculés exactement ? Que devient alors le test == ?

c) Réécrivez la fonction en utilisant une comparaison avec tolérance. On utilisera abs(x - y) < epsilon avec epsilon = 1e-9.

Indice

Remplacer l’égalité par le test d’un écart, en valeur absolue, plus petit que epsilon.

Code à copier puis compléter
def est_rectangle(a, b, c, epsilon=1e-9):
    cotes = sorted([a, b, c])
    return abs(cotes[0]**2 + cotes[1]**2 - ...) < ...

d) Testez les deux versions avec le triangle de côtés $0.5$, $1.2$ et $1.3$. Que constatez-vous ?

Indice

Appeler chaque version avec les côtés 0.5, 1.2 et 1.3, puis afficher 0.5**2 + 1.2**2 et 1.3**2 pour comparer les deux nombres.

Correction publiée après la séance.


Exercice 5 : écrire des assertions et des tests

a) Écrire des assertions de précondition pour la fonction moyenne(L) : la liste ne doit pas être vide et tous ses éléments doivent être des nombres.

Indice

Une première assertion porte sur len(L). Pour la seconde, parcourir la liste et vérifier le type de chaque élément avec isinstance(x, (int, float)).

Code à copier puis compléter
def moyenne(L):
    assert len(L) > 0, "La liste ne doit pas être vide"
    for x in L:
        assert isinstance(x, (int, float)), ...
    return sum(L) / ...

b) Compléter les docstrings de la fonction suivante avec des doctests couvrant les cas normaux et les cas limites :

def est_palindrome(s):
    """
    Renvoie True si la chaîne s est un palindrome, False sinon.

    >>> # Écrire les doctests ici
    """
    return s == s[::-1]
Indice

Un doctest s’écrit >>> est_palindrome("radar"), suivi à la ligne suivante du résultat attendu, True. Prévoir un palindrome, un mot qui n’en est pas un, la chaîne vide et une chaîne d’un seul caractère.

c) Pour la fonction tri_insertion(L), proposer au moins quatre jeux de tests pertinents (liste quelconque, liste vide, liste déjà triée, liste avec doublons) et justifier le choix de chacun.

Indice

Pour chaque jeu de tests, dire quelle erreur de programmation il pourrait révéler : une boucle qui ne s’exécute pas, une liste déjà rangée, des éléments égaux mal placés.

Correction publiée après la séance.


Exercice 6 : chasse aux bugs

Chacun des programmes suivants contient un bug relevant d’une cause classique. Identifiez l’erreur, nommez sa cause et proposez une correction.

a) Indentation incorrecte :

def compter_voyelles(mot):
    voyelles = "aeiouy"
    compteur = 0
    for i in range(len(mot)):
        if mot[i] in voyelles:
            compteur += 1
        return compteur
Indice

Regarder l’indentation du return : à quel bloc appartient-il, et combien de tours de boucle s’exécutent avant lui ?

b) Effet de bord :

def inverser_liste(L):
    resultat = L
    resultat.reverse()
    return resultat

ma_liste = [1, 2, 3]
inversee = inverser_liste(ma_liste)
print(ma_liste)
Indice

resultat = L ne crée pas de copie : les deux noms désignent la même liste. Que fait alors reverse sur ma_liste ?

c) Choix d’inégalité :

def somme_entiers(n):
    s = 0
    i = 1
    while i < n:
        s += i
        i += 1
    return s

print(somme_entiers(5))  # Attendu : 15, obtenu : ?
Indice

Quelles valeurs de i sont ajoutées tant que i < n ? La valeur 5 est-elle prise ?

d) Conditionnelle non exhaustive :

def mention(moyenne):
    if moyenne >= 16:
        return "Très bien"
    elif moyenne >= 14:
        return "Bien"
    elif moyenne >= 12:
        return "Assez bien"

print(mention(10))  # Que se passe-t-il ?
Indice

Pour une moyenne de 10, aucune condition n’est vraie : que renvoie une fonction qui atteint sa fin sans rencontrer de return ?

e) Mauvais nommage :

list = [4, 7, 2, 9]
somme = sum(list)
list = list + [5]     # Que se passe-t-il ici ?
Indice

list est aussi le nom de la fonction prédéfinie qui convertit en liste : que devient-elle après l’affectation de la première ligne ?

f) Comparaison de flottants :

x = 0.1 + 0.1 + 0.1
if x == 0.3:
    print("Égal")
else:
    print("Différent")  # Quel message s'affiche ?
Indice

Comme à l’exercice 4, 0,1 n’a pas d’écriture binaire exacte : la somme de trois 0,1 vaut-elle exactement 0,3 ?

Correction publiée après la séance.


Exercice 7 : gestion des exceptions

a) Écrire une fonction saisie_entier() qui demande à l’utilisateur de saisir un entier et redemande tant que la saisie n’est pas valide. Utiliser try/except.

Indice

Une boucle while True recommence tant qu’il le faut. Dans un bloc try, renvoyer int(...) de la saisie : si la conversion échoue, le bloc except ValueError affiche un message et la boucle recommence.

Code à copier puis compléter
def saisie_entier():
    while True:
        try:
            return int(input(...))
        except ValueError:
            print(...)

b) Écrire une fonction lire_fichier(chemin) qui renvoie le contenu d’un fichier sous forme de chaîne, ou "Fichier introuvable" si le fichier n’existe pas.

Indice

Ouvrir et lire le fichier dans un bloc try ; l’exception levée quand le fichier n’existe pas est FileNotFoundError.

Code à copier puis compléter
def lire_fichier(chemin):
    try:
        with open(chemin, "r") as f:
            return f.read()
    except ...:
        return ...

c) Définir une exception personnalisée NoteInvalideError et une fonction ajouter_note(notes, n) qui lève cette exception si n n’est pas entre 0 et 20.

Indice

Une exception personnalisée est une classe qui hérite de Exception : class NoteInvalideError(Exception):, suivie de pass. La fonction vérifie la note, lève l’exception avec raise NoteInvalideError(...) si elle est hors de l’intervalle, et sinon l’ajoute à la liste.

Correction publiée après la séance.


Exercice 8 : synthèse – débogage complet d’un programme

Le programme suivant est censé lire une liste de notes séparées par des virgules, calculer la moyenne et afficher le résultat avec une mention. Il contient deux bugs et deux situations d’erreur non gérées. Les trouver, les corriger et ajouter une gestion d’erreur appropriée.

def calculer_moyenne(chaine):
    notes = chaine.split(",")
    somme = 0
    for note in notes:
        somme += note
    moyenne = somme / len(notes)
    if moyenne >= 10:
        mention = "admis"
    elif moyenne >= 8:
        mention = "rattrapage"
    return f"Moyenne : {moyenne}/20 ({mention})"

resultat = calculer_moyenne("12,15,8,14,9")
print(resultat)
Indice

Exécuter le programme et lire le message d’erreur : de quel type est chaque note après split ? Chercher ensuite les situations non prévues : une moyenne inférieure à 8, qui laisse mention sans valeur, puis une chaîne vide ou qui contient autre chose que des nombres.

Correction publiée après la séance.