Le contrat d'une fonction
- Leçon 7
- 40 min
- Module 3 : Fonctions et structure du programme
Leçon 7 sur 18 · Python pour l'ingénierie de l'IA en pratiqueLe contrat d'une fonction
1. Bases de l'ordinateur et de l'environnement
2. Valeurs et flux de contrôle
3. Fonctions et structure du programme
4. Fichiers, données et services externes
5. Débogage et fiabilité
6. Du carnet Jupyter au projet facile à maintenir
Ce que vous saurez faire
Divisez count_tickets.py en petites fonctions, chacune avec une seule tâche et un contrat clair. Ce contrat précise les entrées, les sorties et ce que la fonction ne change pas. Vous pourrez vérifier chaque partie séparément et la réutiliser dans d’autres programmes.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter contient tous les exemples, tâches et vérifications de cette leçon, avec les instructions. Il contient aussi le même code de count_tickets.py. Il ne demande aucune installation ni projet local : seulement Python. Lisez les explications ici et exécutez le code là-bas.
Le carnet est en anglais. Colab et Kaggle ouvrent votre propre copie. Il faut un compte Google ou Kaggle gratuit. Vous êtes bloqué ? Consultez le carnet de solution.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Une boucle qui construit un résultat et le comptage avec
.get(key, 0) + 1, dans Conditions et répétitions. - La vérification des données d’entrée vides, dans Conditions et répétitions : à vous de jouer.
- Deux noms pour une même liste, dans Les collections : deux noms, une seule liste. L’erreur courante à la fin utilise cette notion.
Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local; le carnet et les encadrés de cette page n’en ont pas besoin)
count_tickets.pydans~/projects/ticket-cleaner. Il filtre les six billets de soutien par statut, les compte par catégorie et gère les données d’entrée vides. Vous l’avez créé dans Conditions et répétitions : exercices guidés. Vous avez ajouté la vérification des données d’entrée vides et la moyenne dans À vous de jouer : aucun billet correspondant. S’il manque ou s’il est différent, refaites ces deux sections. La réponse se trouve sous Afficher une réponse.- L’environnement
.venvdu projet, actif dans votre terminal (l’invite commence par(.venv)). Vous l’avez créé dans Isoler les dépendances : exercices guidés. S’il manque ou ne fonctionne plus, supprimez-le et recréez-le à partir du fichier de verrouillage. Suivez Recréer l’environnement à partir de zéro.
Testé avec Python 3.14 (terminal) et Pyodide 314.0 (navigateur). Le carnet a été testé dans Jupyter.
Le problème d’un long script
Section intitulée « Le problème d’un long script »À la fin du module 2, count_tickets.py fait tout du début à la fin : filtrer, compter, calculer la moyenne et afficher. Il fonctionne, mais trois choses sont difficiles :
- Pour vérifier une partie, vous devez tout exécuter et lire la sortie affichée.
- Pour réutiliser une partie, par exemple le comptage dans un autre rapport, vous devez copier les lignes.
- Pour le lire, vous devez suivre chaque variable depuis le début, car n’importe quelle ligne peut changer n’importe quelle variable.
Une fonction est un bloc de code nommé que vous pouvez exécuter plusieurs fois. Elle reçoit des valeurs d’entrée, fait une seule tâche et renvoie un résultat. Vous utilisez déjà des fonctions fournies par Python : len(), print() et round(). Maintenant, vous écrivez les vôtres.
Anatomie d’une fonction
Section intitulée « Anatomie d’une fonction »def count_by_category(tickets): """Return the number of tickets in each category.""" counts = {} for ticket in tickets: category = ticket["category"] counts[category] = counts.get(category, 0) + 1 return counts| Partie | Signification |
|---|---|
def |
Commence la définition d’une fonction. Python garde la fonction en mémoire. Il n’exécute pas encore son corps. |
count_by_category |
Le nom. Utilisez un verbe qui indique ce que fait la fonction. |
(tickets) |
Le paramètre : un nom pour la valeur fournie par le code appelant. |
"""Return ...""" |
La chaîne de documentation (docstring) : une phrase qui indique ce que la fonction renvoie. |
| Le corps indenté | Le travail. Il s’exécute chaque fois qu’on appelle la fonction. |
return counts |
Termine la fonction et renvoie la valeur au code appelant. |
Pour appeler la fonction, écrivez son nom avec une valeur entre parenthèses. Cette valeur est l’argument. Python donne la valeur de l’argument au paramètre tickets, exécute le corps et remplace l’appel par la valeur renvoyée :
category_counts = count_by_category(tickets)Le contrat
Section intitulée « Le contrat »Le contrat d’une fonction est sa promesse au code qui l’appelle. Écrivez-le en mots simples avant d’écrire le corps :
| Question | count_by_category |
|---|---|
| Quelles sont les entrées? | Une liste de dictionnaires de billets. Chacun possède une clé "category" (catégorie). |
| Quelles sont les sorties? | Un dictionnaire : catégorie → nombre de billets. Une entrée vide donne {}. |
| Que ne fait pas la fonction? | Elle ne change pas la liste et n’affiche rien. |
La dernière ligne est la plus importante. Une fonction qui lit seulement ses entrées et renvoie un résultat est une fonction pure. Pour les mêmes entrées, elle donne toujours la même sortie et n’a aucun autre effet. Les fonctions pures sont les plus faciles à vérifier : fournissez une entrée, puis comparez la sortie. Gardez les parties qui produisent des effets, comme print ou l’écriture d’un fichier, à quelques endroits en périphérie du programme.
Renvoyer, pas afficher
Section intitulée « Renvoyer, pas afficher »Les débutants écrivent souvent une fonction qui affiche son résultat. Cette fonction peut montrer un résultat, mais aucun autre code ne peut l’utiliser :
def show_count(tickets): print(len(tickets))
result = show_count(tickets) # prints 6print(result) # prints NoneUne fonction sans instruction return renvoie None (aucune valeur). Donc, result vaut None, et vous ne pouvez pas l’utiliser dans un calcul. Renvoyez la valeur et laissez le code appelant décider quoi en faire : l’afficher, l’enregistrer ou la vérifier.
Les variables locales
Section intitulée « Les variables locales »Les variables du corps d’une fonction, comme counts et category, sont locales : elles existent seulement pendant son exécution. Après l’appel, elles n’existent plus. Hors de la fonction, print(counts) s’arrête avec NameError: name 'counts' is not defined (le nom « counts » n’est pas défini).
C’est une protection, pas une limite. Une fonction ne peut pas changer par accident une variable du code appelant. Deux fonctions peuvent utiliser le nom counts sans s’affecter. La seule façon de faire sortir une valeur est return.
Exécutez du Python dans votre navigateur
Appelez une fonction et utilisez le résultat
La même fonction s’exécute trois fois avec des arguments différents. Ajoutez une ligne à la fin : print(counts). Prédisez ce qui se passera, puis exécutez le code.
Tab ajoute quatre espaces. Appuyez sur Esc, puis sur Tab, pour quitter la zone. Ctrl+Enter exécute le code.
Les valeurs par défaut
Section intitulée « Les valeurs par défaut »Un paramètre peut avoir une valeur par défaut. Le code appelant peut omettre cet argument, et la fonction utilise alors la valeur par défaut :
def filter_by_status(tickets, status="open"): """Return the tickets that have the given status.""" selected = [] for ticket in tickets: if ticket["status"] == status: selected.append(ticket) return selected
filter_by_status(tickets) # the open ticketsfilter_by_status(tickets, "closed") # the closed ticketsfilter_by_status(tickets, status="closed")Le dernier appel nomme l’argument : status="closed". Les arguments nommés rendent un appel plus facile à lire, surtout quand une fonction a plus de deux paramètres. Les paramètres avec une valeur par défaut viennent après ceux qui n’en ont pas.
Exemple détaillé : count_tickets.py sous forme de fonctions
Section intitulée « Exemple détaillé : count_tickets.py sous forme de fonctions »Voici le script du module 2, réécrit sous forme de fonctions. Chaque fonction a une seule tâche. main() les appelle dans l’ordre et c’est la seule fonction qui affiche quelque chose.
TICKETS = [ {"id": "T-1001", "status": "open", "category": "billing", "priority": 2}, {"id": "T-1002", "status": "closed", "category": "login", "priority": 3}, {"id": "T-1003", "status": "open", "category": "login", "priority": 1}, {"id": "T-1004", "status": "pending", "category": "billing", "priority": 2}, {"id": "T-1005", "status": "open", "category": "billing", "priority": 3}, {"id": "T-1006", "status": "closed", "category": "shipping", "priority": 1},]
def filter_by_status(tickets, status="open"): """Return the tickets that have the given status.""" selected = [] for ticket in tickets: if ticket["status"] == status: selected.append(ticket) return selected
def count_by_category(tickets): """Return the number of tickets in each category, sorted by category.""" counts = {} for ticket in tickets: category = ticket["category"] counts[category] = counts.get(category, 0) + 1 return dict(sorted(counts.items()))
def average_priority(tickets): """Return the average priority, or None when there are no tickets.""" if not tickets: return None total = 0 for ticket in tickets: total = total + ticket["priority"] return round(total / len(tickets), 1)
def main(): status = "open" selected = filter_by_status(TICKETS, status) print(f"Tickets with status '{status}': {len(selected)}") if not selected: print("No tickets to count.") return for category, count in count_by_category(selected).items(): print(f" {category}: {count}") print(f"Average priority: {average_priority(selected)}")
main()Trois détails :
TICKETSest en majuscules. En Python, cela signifie « une constante : cette valeur ne change pas pendant l’exécution du programme ».average_priorityrenvoieNonepour une entrée vide, au lieu de s’arrêter avecZeroDivisionError(erreur de division par zéro). La chaîne de documentation le précise, donc le code appelant sait qu’il doit vérifier.returnsans valeur dansmain()termine la fonction plus tôt. Ici, il permet de sortir en cas d’entrée vide, commebreakdans une boucle.
La dernière ligne, main(), appelle main. Sans elle, le script définit quatre fonctions, puis se termine sans rien faire.
Exercices guidés : remanier votre script
Section intitulée « Exercices guidés : remanier votre script »Remanier signifie « changer la structure du code sans changer ce qu’il fait ». La sortie doit rester la même avant et après.
-
Exécutez votre
count_tickets.pyactuel. Copiez sa sortie dans un commentaire à la fin du fichier ou dans un fichier texte. Cette sortie servira à la comparaison. -
Remplacez le contenu du fichier par l’exemple détaillé. Gardez vos propres données de billets si vous les avez modifiées.
-
Exécutez-le :
python count_tickets.py.Sortie Tickets with status 'open': 3billing: 2login: 1Average priority: 2.0 -
Comparez la sortie avec celle de l’étape 1. Elle doit être identique. Si elle diffère, trouvez quelle fonction donne une valeur différente. Affichez son résultat dans
main(). -
Dans
main(), changezstatuspour"resolved"(résolu), puis exécutez le script de nouveau. La sortie est le nombre0etNo tickets to count.(aucun billet à compter).
Vérifier chaque fonction séparément
Section intitulée « Vérifier chaque fonction séparément »Chaque fonction est pure, donc quelques lignes suffisent pour la vérifier. Appelez-la avec une petite entrée dont vous pouvez calculer le résultat de tête, puis comparez le résultat. assert arrête le programme avec une erreur si une condition vaut False (faux). Il ne fait rien si elle vaut True (vrai).
Exécutez du Python dans votre navigateur
Vérifiez les fonctions
Chaque assert vérifie un petit fait du contrat. Ajoutez la vérification manquante à la fin. Puis, brisez volontairement la fonction : remplacez return None par return 0 et exécutez-la de nouveau. Quelle ligne échoue?
Tab ajoute quatre espaces. Appuyez sur Esc, puis sur Tab, pour quitter la zone. Ctrl+Enter exécute le code.
Dans le module 5, vous déplacez ces vérifications dans des fichiers de test. Un outil nommé pytest les exécute pour vous.
À vous de jouer : une nouvelle fonction
Section intitulée « À vous de jouer : une nouvelle fonction »Ajoutez une fonction urgent_ids(tickets) à count_tickets.py. Voici son contrat :
| Question | Réponse |
|---|---|
| Quelles sont les entrées? | Une liste de dictionnaires de billets. |
| Quelles sont les sorties? | Une liste des identifiants des billets de priorité 1, dans le même ordre. Une entrée vide donne []. |
| Que ne fait pas la fonction? | Elle ne change pas la liste et n’affiche rien. |
Appelez-la dans main() pour les billets sélectionnés et affichez le résultat. Avec les six billets et le statut "open" (ouvert), la nouvelle ligne est Urgent: ['T-1003'] (urgent : le billet T-1003).
Afficher une réponse
def urgent_ids(tickets): """Return the IDs of the tickets with priority 1, in order.""" return [ticket["id"] for ticket in tickets if ticket["priority"] == 1]Dans main(), après la moyenne : print(f"Urgent: {urgent_ids(selected)}").
Erreur courante
Une liste par défaut qui garde les valeurs en mémoire
Symptôme : une fonction donne un résultat différent à chaque appel avec les mêmes arguments :
def add_problem(problem, problems=[]): problems.append(problem) return problems
print(add_problem("missing id")) # ['missing id']print(add_problem("unknown status")) # ['missing id', 'unknown status']Le deuxième appel devrait renvoyer seulement ['unknown status'] (statut inconnu).
Cause : Python crée la valeur par défaut une seule fois, quand il exécute la ligne def, pas à chaque appel. Une liste peut changer. Chaque appel sans cet argument reçoit donc la même liste, et chaque append y ajoute un élément. C’est le problème de référence partagée présenté dans Les collections, à un endroit moins visible.
Correction : utilisez None comme valeur par défaut et créez une nouvelle liste dans le corps :
def add_problem(problem, problems=None): if problems is None: problems = [] problems.append(problem) return problemsUtilisez seulement des valeurs qui ne peuvent pas changer comme valeurs par défaut : nombres, chaînes, True, False et None. Jamais une liste, un dictionnaire ou un ensemble.
Vérifiez votre compréhension
Une fonction se termine par print(total) et n'a pas de return. Que donne result = the_function(...), et pourquoi est-ce un problème?
Afficher la réponse
result vaut None, car une fonction sans return renvoie None. Le total s’affiche à l’écran, mais le programme ne peut pas l’utiliser. Il ne peut pas le comparer, l’enregistrer ou le vérifier avec assert. Renvoyez la valeur et affichez-la là où vous appelez la fonction.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Vérification du module, partie 1 : expliquez le contrat de filter_by_status en mots simples, puis prouvez-en deux éléments avec assert.
Afficher la réponse
Entrées : une liste de dictionnaires de billets et un statut (par défaut, "open"). Sortie : une nouvelle liste contenant seulement les billets ayant ce statut, dans le même ordre. Une entrée vide donne une liste vide. La fonction ne change pas la liste d’entrée et n’affiche rien. Deux vérifications : assert filter_by_status([]) == [] et, pour une liste avec un billet ouvert et un billet fermé, assert len(filter_by_status(two, "closed")) == 1.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Une fonction a des paramètres, un corps et un
return. L’appel donne aux paramètres les valeurs des arguments et devient la valeur renvoyée. - Écrivez d’abord le contrat : les entrées, les sorties et ce que la fonction ne change pas.
- Une fonction pure lit seulement ses entrées et renvoie un résultat. C’est le type de fonction le plus facile à vérifier avec
assert. - Les variables d’une fonction sont locales. Les valeurs sortent d’une fonction seulement par
return. - N’utilisez jamais une liste, un dictionnaire ou un ensemble comme valeur par défaut. Utilisez
None, puis créez-en un nouveau dans le corps.
Ensuite, vous déplacez les fonctions du script vers un module pour que d’autres programmes puissent les importer.
Leçon suivante : Modules et importations