Prérequis : Les fonctions, Structures conditionnelles.
def, entre triples guillemets : rôle de la fonction, paramètres, valeur renvoyée, exemple. Elle s'affiche avec help(f).assertassert condition, "message affiché si la condition est fausse"
AssertionError et affiche le message.assert dans une fonction test_nom(), appelée après chaque modification du code.assert(cond, "msg") avec des parenthèses : le couple est un tuple non vide, donc toujours vrai, et l'assertion ne détecte plus rien.== : 0.1 + 0.2 == 0.3 vaut False.print et return : une fonction qui affiche le bon résultat mais renvoie None échoue à tous les tests.| Code erroné | Code correct | Explication |
|---|---|---|
assert(x > 0, "x positif attendu") | assert x > 0, "x positif attendu" | Avec les parenthèses, Python évalue le tuple (x > 0, "..."), qui est toujours vrai : l'assertion ne se déclenche jamais. Python signale d'ailleurs un SyntaxWarning. |
def moyenne(L): return sum(L) / len(L) sans précondition | assert len(L) > 0, "liste vide" en première ligne du corps | Sur une liste vide, la division par zéro provoque un ZeroDivisionError peu explicite ; la précondition donne un message clair et documente le contrat. |
assert aire(1) == 3.141592653589793 | assert abs(aire(1) - 3.14159) < 1e-4 | Les flottants sont des valeurs approchées : on compare à une tolérance, jamais avec l'égalité stricte. |
def double(x): return 2 * x """Double x.""" | Docstring en première ligne du corps, avant le code | Placée après le return, la chaîne n'est jamais atteinte et help(double) ne l'affiche pas. |
def moyenne(L):
"""Renvoie la moyenne des nombres de la liste L.
Précondition : L est une liste non vide de nombres.
Postcondition : le résultat est compris entre min(L) et max(L).
Exemple : moyenne([10, 14, 12]) renvoie 12.0
"""
assert len(L) > 0, "moyenne : la liste ne doit pas être vide"
total = 0
for x in L:
total += x
return total / len(L)
print(moyenne([10, 14, 12])) # 12.0
moyenne([])
Python s'arrête et affiche la dernière ligne du message d'erreur :
AssertionError: moyenne : la liste ne doit pas être vide
def est_bissextile(annee):
"""Renvoie True si annee est bissextile (calendrier grégorien)."""
return (annee % 4 == 0 and annee % 100 != 0) or annee % 400 == 0
def test_est_bissextile():
assert est_bissextile(2024) == True # divisible par 4
assert est_bissextile(2023) == False # cas normal
assert est_bissextile(1900) == False # divisible par 100, pas par 400
assert est_bissextile(2000) == True # divisible par 400
print("test_est_bissextile : tous les tests passent")
test_est_bissextile()
Un jeu de tests pour maximum(L) :
| Famille | Entrée | Résultat attendu |
|---|---|---|
| cas normal | [3, 7, 2] | 7 |
| un seul élément | [5] | 5 |
| tous négatifs | [-3, -7, -2] | -2 |
| doublons | [4, 4, 4] | 4 |
| maximum en première position | [9, 1, 2] | 9 |
| maximum en dernière position | [1, 2, 9] | 9 |
def maximum(L):
maxi = 0 # erreur : initialisation à 0
for x in L:
if x > maxi:
maxi = x
return maxi
assert maximum([3, 7, 2]) == 7 # passe : le bug reste invisible
assert maximum([-3, -7, -2]) == -2 # échoue : AssertionError
Le second test échoue, car maximum([-3, -7, -2]) renvoie 0. Le cas limite « tous négatifs » révèle l'erreur d'initialisation ; la correction est maxi = L[0].
def racine_entiere(n):
"""Renvoie le plus grand entier k tel que k * k <= n.
Précondition : n est un entier positif ou nul.
"""
assert isinstance(n, int) and n >= 0, "n doit être un entier positif ou nul"
k = 0
while (k + 1) * (k + 1) <= n:
k += 1
assert k * k <= n < (k + 1) * (k + 1) # postcondition
return k
print(racine_entiere(15), racine_entiere(16), racine_entiere(2026)) # 3 4 45
On veut une fonction division_euclidienne(a, b) qui renvoie le couple (q, r) du quotient et du reste de la division de a par b, sans utiliser les opérateurs // et %.
a et b, postcondition reliant a, b, q et r.test_division_euclidienne() avec quatre tests, dont les cas a = 0, a < b et a multiple de b.def division_euclidienne(a, b):
"""Renvoie (q, r), quotient et reste de la division euclidienne de a par b.
Préconditions : a et b sont des entiers, a >= 0 et b > 0.
Postcondition : a == b * q + r et 0 <= r < b.
"""
assert isinstance(a, int) and isinstance(b, int), "a et b entiers attendus"
assert a >= 0 and b > 0, "il faut a >= 0 et b > 0"
q = 0
r = a
while r >= b:
r = r - b
q = q + 1
assert a == b * q + r and 0 <= r < b
return (q, r)
def test_division_euclidienne():
assert division_euclidienne(7, 3) == (2, 1) # cas normal
assert division_euclidienne(0, 5) == (0, 0) # a = 0
assert division_euclidienne(3, 5) == (0, 3) # a < b
assert division_euclidienne(12, 4) == (3, 0) # a multiple de b
print("test_division_euclidienne : tous les tests passent")
test_division_euclidienne()
La fonction suivante doit compter les éléments strictement positifs d'une liste.
def nb_positifs(L):
"""Renvoie le nombre d'éléments strictement positifs de L."""
n = 0
for x in L:
if x >= 0:
n += 1
return n
assert nb_positifs([1, 2, 3]) == 3 # passe
assert nb_positifs([-1, -2]) == 0 # passe
assert nb_positifs([0, 1, -3]) == 1 # échoue : renvoie 2
Cas révélateur : la présence de 0. Avec >=, le zéro est compté alors qu'il n'est pas strictement positif. Les deux premiers tests ne contiennent pas de zéro, donc > et >= donnent le même résultat : le bug est invisible.
Correction : remplacer if x >= 0: par if x > 0:. Le troisième test passe alors : nb_positifs([0, 1, -3]) renvoie 1.
def nb_voyelles(mot):
"""Renvoie le nombre de voyelles (a, e, i, o, u, y) de mot, majuscules comprises."""
compteur = 0
for lettre in mot:
if lettre.lower() in "aeiouy":
compteur += 1
return compteur
test_nb_voyelles() avec au moins quatre tests : un mot sans voyelle, la chaîne vide, un mot en majuscules, un mot formé uniquement de voyelles..lower(). Lequel de tes tests échoue avec sa version ? Quelle valeur renvoie-t-elle ?def test_nb_voyelles():
assert nb_voyelles("rythm") == 1 # une seule voyelle, le y
assert nb_voyelles("xkcd") == 0 # sans voyelle
assert nb_voyelles("") == 0 # chaîne vide
assert nb_voyelles("PYTHON") == 2 # majuscules : Y et O
assert nb_voyelles("aeiouy") == 6 # que des voyelles
print("test_nb_voyelles : tous les tests passent")
test_nb_voyelles()
Sans .lower(), seul le test nb_voyelles("PYTHON") == 2 échoue : la version fautive compare "P", "Y", … à des lettres minuscules et renvoie 0. C'est le cas « majuscules » qui révèle l'oubli ; tous les autres tests passent.
assert(x >= 0, "x doit être positif") ne signale jamais d'erreur, même pour x = -5. Corriger.chiffres(n) qui renvoie la liste des chiffres de l'entier n (précondition : n entier positif ou nul), par exemple chiffres(2026) renvoie [2, 0, 2, 6]. Vérifier la précondition par une assertion, puis, avant le return, vérifier par une assertion la postcondition : les chiffres renvoyés recomposent bien n.0, 7 et 2026.(x >= 0, "x doit être positif"). Un tuple non vide est toujours considéré comme vrai, donc l'assertion ne se déclenche jamais. Correction : assert x >= 0, "x doit être positif", sans parenthèses.
def chiffres(n):
"""Renvoie la liste des chiffres de n, de gauche à droite.
Précondition : n est un entier positif ou nul.
Postcondition : les chiffres renvoyés recomposent n.
"""
assert isinstance(n, int) and n >= 0, "n doit être un entier positif ou nul"
if n == 0:
return [0]
L = []
reste = n
while reste > 0:
L.append(reste % 10)
reste = reste // 10
L.reverse()
valeur = 0
for c in L:
valeur = valeur * 10 + c
assert valeur == n, "les chiffres ne recomposent pas n"
return L
chiffres(0) renvoie [0], chiffres(7) renvoie [7], chiffres(2026) renvoie [2, 0, 2, 6]. Le cas n = 0 est traité à part : sans lui, la boucle ne s'exécute pas et la fonction renverrait la liste vide.