La portée des variables et les tests
Ce qui se passe dans une fonction reste dans la fonction
def calculer():
resultat = 42
return resultat
calculer()
print(resultat)
NameError: name 'resultat' is not defined
Surprise : resultat a pourtant été créée ! Oui, mais à l'intérieur de la fonction.
Pour récupérer un résultat calculé dans une fonction, il n'y a qu'un moyen propre : le renvoyer avec return, et le ranger à l'appel :
resultat = calculer()
Deux variables, même nom
x = 10
def changer():
x = 99 # crée une NOUVELLE variable x, locale
changer()
print(x) # 10 : le x global n'a pas bougé
Dans la fonction, x = 99 crée une variable locale qui porte le même nom. Elle cache la globale pendant l'appel, puis disparaît. La globale reste intacte.
Python permet de modifier une globale depuis une fonction avec le mot-clé global. Évite-le : c'est une source de bugs très difficiles à trouver, comme le montre l'histoire suivante.
Documenter : les docstrings
Une docstring est un texte placé juste sous la ligne def, entre trois guillemets, qui explique ce que fait la fonction, ce qu'elle attend et ce qu'elle renvoie.
def prix_ttc(prix_ht, taux=0.1925):
"""Renvoie le prix toutes taxes comprises, arrondi au franc.
prix_ht : le prix hors taxes, en FCFA.
taux : le taux de TVA, 19,25 % par défaut (Cameroun).
"""
return round(prix_ht * (1 + taux))
Les trois guillemets permettent d'écrire un texte sur plusieurs lignes. Contrairement à un commentaire #, la docstring est gardée par Python : help(prix_ttc) l'affiche. Les éditeurs comme VS Code la montrent quand tu survoles un appel.
Une bonne docstring dit quoi et pour quoi, pas comment : le code dit déjà comment.
Tester ses fonctions avec `assert`
Depuis le début, un correcteur vérifie tes exercices en appelant tes fonctions avec plusieurs valeurs. Les développeurs professionnels font exactement pareil avec leur propre code : ils écrivent des tests.
Un test unitaire est un petit morceau de code qui appelle une fonction avec des valeurs connues et vérifie qu'elle renvoie le bon résultat.
assert condition vérifie qu'une condition est vraie. Si elle l'est, il ne se passe rien. Si elle est fausse, Python s'arrête avec une AssertionError.
def prix_ttc(prix_ht, taux=0.1925):
return round(prix_ht * (1 + taux))
assert prix_ttc(1000) == 1192
assert prix_ttc(0) == 0
assert prix_ttc(1000, 0.05) == 1050
print("Tous les tests passent")
Si un jour quelqu'un modifie prix_ttc et la casse, les tests le diront immédiatement, avant que la bêtise n'arrive chez les clients.
- un cas normal (1000 FCFA) ;
- les cas limites : 0, une liste vide, une note pile à 10 ;
- les cas bizarres : un nombre négatif, un texte avec des espaces.
Les bugs se cachent presque toujours dans les cas limites.
Kent Beck et Erich Gamma · Développeurs et auteurs · 1997
En 1997, deux développeurs renommés, Kent Beck et Erich Gamma, prennent ensemble un vol de Zurich vers Atlanta, pour une conférence. Pour passer le temps, ils écrivent à deux, sur un ordinateur portable, un petit outil pour tester automatiquement du code Java : JUnit.
Cet outil, écrit en quelques heures au-dessus de l'Atlantique, devient l'un des logiciels les plus utilisés de l'histoire. Il inspire des outils de test dans tous les langages, dont pytest pour Python, que tu utiliseras dans tes vrais projets.
Kent Beck popularise ensuite une méthode étonnante, le développement piloté par les tests (TDD) : on écrit d'abord le test, qui échoue forcément puisque la fonction n'existe pas encore, puis le code qui le fait passer. C'est exactement ce que tu fais depuis le début de ce parcours : les tests existent avant ton code, et tu écris le code jusqu'à ce qu'ils passent.
Exercice : Répare : la variable perdue
Ce programme veut afficher le total d'une commande, mais il plante avec une NameError. Corrige-le sans utiliser le mot global : la fonction doit renvoyer son résultat.
Voir une solution
def calculer_total(prix):
total = 0
for p in prix:
total += p
return total
total = calculer_total([500, 1200, 300])
print(f"Total : {total} FCFA")Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice
Exercice : Écris toi-même les tests
La fonction est_bissextile(annee) est donnée. Écris en dessous au moins quatre assert qui la testent : 2024 (oui), 2026 (non), 1900 (non), 2000 (oui). Puis affiche Tous les tests passent.
Ensuite, pour voir un test échouer, remplace volontairement 400 par 300 dans la fonction et relance : lis l'AssertionError. Remets 400 avant de vérifier.
Voir une solution
def est_bissextile(annee):
"""Renvoie True si l'année est bissextile (calendrier grégorien)."""
return (annee % 4 == 0 and annee % 100 != 0) or annee % 400 == 0
assert est_bissextile(2024)
assert not est_bissextile(2026)
assert not est_bissextile(1900)
assert est_bissextile(2000)
print("Tous les tests passent")Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice
Exercice : D'abord le test, ensuite le code
Fais comme Kent Beck. Les tests de nettoyer_telephone(numero) sont déjà écrits en bas. Ils échouent, puisque la fonction ne fait encore rien. Écris la fonction pour qu'ils passent tous.
Elle doit renvoyer le numéro sans espaces, sans tirets et sans points, et sans l'indicatif +237 du Cameroun s'il est présent au début.
Voir une solution
def nettoyer_telephone(numero):
"""Renvoie le numéro sans séparateurs ni indicatif +237."""
numero = numero.replace(" ", "").replace("-", "").replace(".", "")
if numero.startswith("+237"):
numero = numero[4:]
return numero
assert nettoyer_telephone("699 12 34 56") == "699123456"
assert nettoyer_telephone("699-12-34-56") == "699123456"
assert nettoyer_telephone("+237 699 12 34 56") == "699123456"
assert nettoyer_telephone("+237699.12.34.56") == "699123456"
print("Tous les tests passent")Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice
Teste-toi
Qu'affiche ce code ?
python total = 0 def ajouter(n): total = n ajouter(5) print(total)
50(bonne réponse)- Une erreur
None
Dans la fonction, total = n crée une variable locale. La variable globale total reste à 0. Pour obtenir 5, il faut que la fonction renvoie la valeur et qu'on la range : total = ajouter(5) avec un return.