Chapitre 627 minDébutant
Fonctions et procédures
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- distinguer une fonction d'une procédure ;
- écrire, appeler et documenter une fonction et ses paramètres ;
- choisir entre afficher et renvoyer un résultat ;
- distinguer variables locales et globales ;
- découper un problème en sous-programmes par la méthode descendante.
6 exercices corrigés
Une fonction est un sous-programme nommé qui reçoit des paramètres et renvoie une valeur ; une procédure est un sous-programme qui effectue une action (afficher, modifier un tableau) sans renvoyer de valeur. Ce chapitre explique comment les écrire en pseudo-code et en Python, comment passer des paramètres en entrée, en sortie ou en entrée-sortie, ce qui distingue variables locales et globales, et comment découper un problème avec la méthode descendante.
Pourquoi découper en sous-programmes ?
Imagine un programme de 200 lignes qui calcule trois fois une moyenne, à trois endroits différents. Tu trouves une erreur dans le calcul : il faut la corriger trois fois, et tu en oublieras une. Les sous-programmes règlent ce problème et quelques autres.
| Bénéfice | Concrètement |
|---|---|
| Ne pas se répéter | Le calcul de la moyenne est écrit une fois, appelé partout |
| Lire facilement | moyenne(notes) se comprend sans lire le détail |
| Tester séparément | Chaque fonction a son jeu d'essai (chapitre 5) |
| Réutiliser | Une fonction bien écrite resservira dans un autre programme |
| Travailler à plusieurs | Chacun écrit ses fonctions, à condition de s'accorder sur leur interface |
Le programme du BTS demande de connaître les concepts de modularité (découper en modules indépendants) et d'interface. L'interface d'une fonction, c'est ce qu'il faut savoir pour l'utiliser sans lire son code : son nom, ses paramètres avec leur type et leur rôle, et ce qu'elle renvoie. On l'appelle aussi sa signature. C'est un contrat : tant que l'interface ne change pas, on peut réécrire l'intérieur sans toucher au reste du programme.
Fonction ou procédure ?
| Fonction | Procédure | |
|---|---|---|
| Rôle | Calculer et renvoyer une valeur | Effectuer une action |
| Résultat | Une valeur (ou plusieurs), récupérée par l'appelant | Pas de valeur renvoyée |
| Appel | Dans une expression : m ← Moyenne(t) | Comme une instruction : AfficherBulletin(t) |
| Exemples | Moyenne, maximum, est_pair, prix_ttc | Afficher un menu, trier un tableau sur place, écrire un fichier |
En Python, il n'existe qu'un seul mot-clé, def. Une « procédure » est simplement une fonction sans return : elle renvoie automatiquement la valeur spéciale None. En C#, la distinction est visible dans le type de retour : void pour une procédure. En JavaScript, comme en Python, tout s'écrit function (les fonctions en JavaScript).
Écrire et appeler une fonction
En pseudo-code, une fonction déclare le type de chaque paramètre et le type de la valeur renvoyée. Retourner renvoie la valeur et termine la fonction.
Fonction PrixTTC(ht : réel, taux : réel) : réel
// Rôle : calculer un prix TTC
// Entrées : ht, le prix hors taxe ; taux, le taux de TVA en %
// Sortie : le prix TTC
Début
Retourner ht × (1 + taux / 100)
FinFonction
// Programme principal
Début
Afficher(PrixTTC(100, 20))
total ← PrixTTC(50, 20) + PrixTTC(15, 10)
Afficher(total)
Fin
def prix_ttc(ht, taux):
"""Renvoie le prix TTC d'un prix hors taxe ht, pour un taux de TVA en %."""
return ht * (1 + taux / 100)
print(prix_ttc(100, 20))
total = prix_ttc(50, 20) + prix_ttc(15, 10)
print(round(total, 2))
120.0
76.5
Le vocabulaire à connaître :
- les paramètres formels sont les noms de la définition (
ht,taux) : des variables qui n'existent que pendant l'appel ; - les paramètres effectifs, ou arguments, sont les valeurs passées à l'appel (
100,20) ; - l'appel associe chaque argument au paramètre de même rang, exécute le corps, puis remplace l'appel par la valeur renvoyée.
En Python, la définition doit être exécutée avant l'appel : on place les def en haut du fichier, et le programme principal en dessous.
Python ajoute deux facilités. Un paramètre peut avoir une valeur par défaut, utilisée si l'argument est omis, et on peut nommer les arguments à l'appel :
def prix_ttc(ht, taux=20):
return ht * (1 + taux / 100)
print(prix_ttc(100)) # taux vaut 20 par défaut
print(prix_ttc(100, taux=5.5)) # argument nommé
Return : renvoyer, et s'arrêter
return termine la fonction immédiatement, même au milieu d'une boucle. C'est pratique pour une recherche : dès qu'on a trouvé, inutile de continuer.
def contient_admin(comptes):
"""Renvoie True si la liste de comptes contient "admin"."""
for compte in comptes:
if compte == "admin":
return True # trouvé : on sort tout de suite
return False # la boucle est finie sans rien trouver
print(contient_admin(["jdupont", "admin", "mleroy"]))
print(contient_admin(["jdupont", "mleroy"]))
True
False
Une fonction peut renvoyer plusieurs valeurs. Python les regroupe dans un couple (un tuple), qu'on récupère avec une affectation multiple :
def min_max(valeurs):
"""Renvoie le plus petit et le plus grand élément d'une liste non vide."""
petit = valeurs[0]
grand = valeurs[0]
for v in valeurs:
if v < petit:
petit = v
if v > grand:
grand = v
return petit, grand
mini, maxi = min_max([14, 8, 17, 11])
print(mini, maxi)
8 17
Afficher ou renvoyer ?
C'est l'erreur de conception la plus fréquente chez les débutants : écrire une fonction qui affiche son résultat au lieu de le renvoyer.
def moyenne_affiche(notes):
print(sum(notes) / len(notes))
m = moyenne_affiche([12, 15, 9])
print("Résultat récupéré :", m)
12.0
Résultat récupéré : None
La moyenne s'affiche, mais l'appelant ne récupère rien (None) : impossible de la comparer à 10, de l'arrondir ou de la stocker. Une fonction de calcul renvoie ; c'est le programme principal (ou une procédure d'affichage) qui décide d'afficher. Cette séparation entre calcul et affichage est la même qu'entre le modèle et la vue dans une architecture MVC, que tu retrouveras en SLAM.
Paramètres en entrée, en sortie, en entrée-sortie
Le programme demande de « gérer les transferts en entrée seule, en sortie seule, en entrée et sortie ». En pseudo-code, on précise le mode de chaque paramètre d'une procédure :
| Mode | Notation | Sens | Exemple |
|---|---|---|---|
| Entrée | E | Le sous-programme lit la valeur, sans la modifier pour l'appelant | Le taux de TVA |
| Sortie | S | Le sous-programme y écrit un résultat, que l'appelant récupère | Le quotient et le reste d'une division |
| Entrée-sortie | E/S | Le sous-programme lit la valeur et la modifie | Un tableau à trier sur place |
Procédure Diviser(E a : entier, E b : entier, S q : entier, S r : entier)
Début
q ← a DIV b
r ← a MOD b
FinProcédure
Procédure Augmenter(E/S prix : tableau de réels, E taux : réel)
Début
Pour i de 0 à longueur(prix) - 1 Faire
prix[i] ← prix[i] × (1 + taux / 100)
FinPour
FinProcédure
D'autres langages ont des mots-clés pour cela (out et ref en C#, & en PHP). Python n'en a pas : il transmet à la fonction une référence vers la valeur, et le comportement dépend de ce qu'on fait de cette valeur.
- Entrée : c'est le cas normal. Réaffecter un paramètre dans la fonction (
n = n * 2) ne change pas la variable de l'appelant. - Sortie : on la remplace par une valeur renvoyée avec
return, éventuellement plusieurs. - Entrée-sortie : si le paramètre est une liste, la fonction peut la modifier sur place (
t[i] = …,t.append(…)), et l'appelant voit la modification. Pour un nombre ou une chaîne, qu'on ne peut pas modifier sur place, on renvoie la nouvelle valeur et l'appelant la réaffecte.
def diviser(a, b):
"""Sortie : renvoie le quotient et le reste."""
return a // b, a % b
def doubler(n):
"""Entrée : la réaffectation reste locale."""
n = n * 2
return n
def augmenter(prix, taux):
"""Entrée-sortie : modifie la liste reçue, sur place."""
for i in range(len(prix)):
prix[i] = round(prix[i] * (1 + taux / 100), 2)
q, r = diviser(17, 5)
print(q, r)
x = 5
y = doubler(x)
print(x, y)
tarifs = [10.0, 25.0, 40.0]
augmenter(tarifs, 10)
print(tarifs)
3 2
5 10
[11.0, 27.5, 44.0]
Après l'appel doubler(x), x vaut toujours 5 : le paramètre n était une variable locale qui a reçu la valeur 5, puis a été réaffectée. Après augmenter(tarifs, 10), la liste tarifs a changé : la fonction a travaillé sur la même liste que l'appelant, pas sur une copie. Le mécanisme des références de listes est détaillé au chapitre 7.
Variables locales et globales
Une variable créée dans une fonction (y compris ses paramètres) est locale : elle naît à l'appel, disparaît à la fin, et n'est pas visible ailleurs. Deux fonctions peuvent donc avoir chacune une variable i sans conflit. Une variable créée hors de toute fonction, dans le programme principal, est globale : toutes les fonctions peuvent la lire.
TAUX_TVA = 20 # globale : lisible partout
def prix_ttc(ht):
resultat = ht * (1 + TAUX_TVA / 100) # resultat est locale
return resultat
print(prix_ttc(50))
print(TAUX_TVA)
# print(resultat) -> NameError : resultat n'existe pas ici
Lire une globale dans une fonction fonctionne. La modifier est un piège : dès qu'une fonction affecte une variable, Python considère que cette variable est locale à la fonction.
compteur = 0
def incrementer():
compteur = compteur + 1 # UnboundLocalError
incrementer()
UnboundLocalError: cannot access local variable 'compteur' where it is not associated with a value
Python voit l'affectation compteur = …, décide que compteur est locale, puis constate qu'on la lit avant de lui avoir donné une valeur. Le mot-clé global compteur, en première ligne de la fonction, lève l'ambiguïté. Mais la bonne solution est presque toujours de passer la valeur en paramètre et de renvoyer la nouvelle :
def incrementer(compteur):
return compteur + 1
compteur = 0
compteur = incrementer(compteur)
print(compteur)
Documenter une fonction
En Python, la documentation d'une fonction s'écrit dans une docstring : une chaîne entre triples guillemets, en première ligne du corps. Elle reprend l'en-tête recommandé par le programme : rôle, entrées, sortie, et les conditions d'utilisation (on dit préconditions).
def moyenne(notes):
"""Calcule la moyenne d'une liste de notes.
Entrée : notes, une liste non vide de nombres.
Sortie : la moyenne, un réel.
"""
total = 0
for note in notes:
total = total + note
return total / len(notes)
help(moyenne)
help(moyenne) affiche la docstring : c'est ainsi que fonctionne l'aide de Python sur ses propres fonctions, et c'est une aide que tu auras toujours sous la main le jour de l'épreuve, où internet est interdit. La précondition « liste non vide » prévient l'appelant : avec une liste vide, la fonction lève ZeroDivisionError, et c'est à lui de tester avant d'appeler.
La méthode descendante
La méthode descendante (ou analyse descendante) consiste à découper un problème complexe en sous-problèmes plus simples, puis à découper chacun d'eux, jusqu'à obtenir des tâches assez petites pour être écrites directement. Chaque tâche devient une fonction ou une procédure. On part du général pour aller au particulier.
Prenons l'énoncé : « Saisir les notes d'une classe, puis afficher la moyenne, la meilleure note et le nombre d'étudiants au-dessus de la moyenne. »
Niveau 1, le problème découpé en trois étapes :
- Saisir les notes.
- Calculer les résultats : la moyenne, la meilleure note, le nombre au-dessus de la moyenne.
- Afficher le bilan.
Niveau 2, chaque étape devient un sous-programme avec son interface :
| Sous-programme | Type | Entrées | Sortie |
|---|---|---|---|
saisir_notes(n) | fonction | n, le nombre d'étudiants | la liste des notes |
moyenne(notes) | fonction | la liste | un réel |
maximum(notes) | fonction | la liste | la plus grande note |
compter_au_dessus(notes, seuil) | fonction | la liste, un seuil | un entier |
afficher_bilan(moy, maxi, nb) | procédure | les trois résultats | rien (affichage) |
Le programme principal se contente alors d'enchaîner les appels. Il se lit comme l'énoncé :
def saisir_notes(n):
notes = []
for i in range(n):
note = float(input(f"Note {i + 1} ? "))
notes.append(note)
return notes
def moyenne(notes):
total = 0
for note in notes:
total = total + note
return total / len(notes)
def maximum(notes):
plus_grande = notes[0]
for note in notes:
if note > plus_grande:
plus_grande = note
return plus_grande
def compter_au_dessus(notes, seuil):
nb = 0
for note in notes:
if note > seuil:
nb = nb + 1
return nb
def afficher_bilan(moy, maxi, nb):
print("Moyenne :", round(moy, 2))
print("Meilleure note :", maxi)
print("Au-dessus de la moyenne :", nb)
# Programme principal
notes = saisir_notes(4)
moy = moyenne(notes)
afficher_bilan(moy, maximum(notes), compter_au_dessus(notes, moy))
Avec les notes 12, 15, 3 et 7 :
Note 1 ? 12
Note 2 ? 15
Note 3 ? 3
Note 4 ? 7
Moyenne : 9.25
Meilleure note : 15.0
Au-dessus de la moyenne : 2
Chaque fonction se teste seule, avec quelques assert, avant d'assembler. Et si demain on veut lire les notes depuis un fichier, seule saisir_notes change. Les listes (notes.append) sont présentées au chapitre 7, les f-strings (f"Note {i + 1} ? ") au chapitre 9.
Exercices
Exercice 1 sur 6 Écrire des fonctions simples Réussi À revoir
Écris en Python, avec une docstring d'une ligne :
est_pair(n), qui renvoieTruesinest pair ;valeur_absolue(x), sans utiliserabs;max2(a, b), qui renvoie le plus grand de deux nombres, puismax3(a, b, c)qui utilisemax2.
Voir la réponseMasquer la réponse de l'exercice 1
def est_pair(n):
"""Renvoie True si n est pair."""
return n % 2 == 0
def valeur_absolue(x):
"""Renvoie la valeur absolue de x."""
if x < 0:
return -x
return x
def max2(a, b):
"""Renvoie le plus grand de a et b."""
if a > b:
return a
return b
def max3(a, b, c):
"""Renvoie le plus grand de a, b et c."""
return max2(max2(a, b), c)
assert est_pair(4) and not est_pair(7)
assert valeur_absolue(-3) == 3 and valeur_absolue(2) == 2
assert max3(4, 9, 2) == 9 and max3(9, 4, 2) == 9 and max3(2, 4, 9) == 9
print("OK")
max3 réutilise max2 : c'est la modularité en petit. Remarque que le else est inutile après un return.
Tu avais trouvé ?
Exercice 2 sur 6 Trace d'appels Réussi À revoir
Qu'affiche ce programme ? Fais la trace en distinguant les variables du programme principal et celles de la fonction.
def f(a, b):
a = a + b
b = a * 2
return a - b
a = 3
b = 4
c = f(b, a)
print(a, b, c)
Voir la réponseMasquer la réponse de l'exercice 2
L'appel f(b, a) passe les valeurs 4 et 3 : dans la fonction, le paramètre a vaut 4 et le paramètre b vaut 3. Les noms sont les mêmes que dans le programme principal, mais ce sont des variables locales distinctes.
| Où | a | b | Valeur renvoyée |
|---|---|---|---|
| Principal, avant l'appel | 3 | 4 | |
| Dans f, au début | 4 | 3 | |
Dans f, a ← a + b | 7 | 3 | |
Dans f, b ← a × 2 | 7 | 14 | |
Dans f, return a - b | −7 | ||
| Principal, après l'appel | 3 | 4 | c = −7 |
Le programme affiche 3 4 -7. Les variables a et b du programme principal n'ont pas changé.
Tu avais trouvé ?
Exercice 3 sur 6 Procédure ou fonction ? Réussi À revoir
Pour chaque besoin, dis s'il faut une fonction ou une procédure, et écris son interface (nom, paramètres avec leur mode, valeur renvoyée).
- Calculer l'âge d'une personne à partir de son année de naissance et de l'année en cours.
- Afficher un menu de cinq choix.
- Mettre tous les noms d'une liste en majuscules, dans la liste elle-même.
- Calculer la somme et la moyenne d'une liste de montants.
Voir la réponseMasquer la réponse de l'exercice 3
- Fonction
Age(E naissance : entier, E annee : entier) : entier. - Procédure
AfficherMenu(), sans paramètre : elle n'a rien à calculer ni à renvoyer. - Procédure
MettreEnMajuscules(E/S noms : tableau de chaînes): la liste est modifiée sur place. - Procédure
SommeMoyenne(E montants : tableau de réels, S somme : réel, S moyenne : réel)en pseudo-code ; en Python, une fonction qui renvoie deux valeurs :return somme, moyenne.
Tu avais trouvé ?
Exercice 4 sur 6 Locale ou globale ? Réussi À revoir
Qu'affiche ce programme ?
x = 10
def g():
x = 5
return x + 1
def h():
return x + 1
print(g(), h(), x)
Voir la réponseMasquer la réponse de l'exercice 4
6 11 10. Dans g, l'affectation x = 5 crée une variable locale x, qui masque la globale pendant l'appel : g() renvoie 6. Dans h, il n'y a pas d'affectation de x, donc x désigne la globale : h() renvoie 11. La globale x n'a jamais été modifiée : elle vaut toujours 10.
Tu avais trouvé ?
Exercice 5 sur 6 Corriger une fonction qui affiche Réussi À revoir
Cette fonction doit permettre d'écrire if est_majeur(17): …, mais le test ne marche pas. Pourquoi ? Corrige-la.
def est_majeur(age):
if age >= 18:
print(True)
else:
print(False)
Voir la réponseMasquer la réponse de l'exercice 5
La fonction affiche True ou False mais ne renvoie rien : elle renvoie donc None, qui vaut faux dans un if. Le test est toujours faux, même pour un majeur. Correction :
def est_majeur(age):
return age >= 18
Tu avais trouvé ?
Exercice 6 sur 6 Méthode descendante : gestion de stock Réussi À revoir
Une boutique suit son stock dans deux listes de même longueur : articles (les noms) et quantites. Le gérant veut un programme qui affiche les articles en rupture (quantité 0), le nombre d'articles à recommander (quantité inférieure à 5) et la quantité totale en stock. Fais l'analyse descendante (sous-programmes et interfaces), puis écris le programme avec articles = ["clavier", "souris", "écran", "câble HDMI"] et quantites = [12, 0, 3, 40].
Voir la réponseMasquer la réponse de l'exercice 6
| Sous-programme | Type | Entrées | Sortie |
|---|---|---|---|
ruptures(articles, quantites) | fonction | les deux listes | la liste des articles à 0 |
nb_a_recommander(quantites, seuil) | fonction | quantités, seuil | un entier |
total_stock(quantites) | fonction | quantités | un entier |
def ruptures(articles, quantites):
resultat = []
for i in range(len(articles)):
if quantites[i] == 0:
resultat.append(articles[i])
return resultat
def nb_a_recommander(quantites, seuil):
nb = 0
for q in quantites:
if q < seuil:
nb = nb + 1
return nb
def total_stock(quantites):
total = 0
for q in quantites:
total = total + q
return total
articles = ["clavier", "souris", "écran", "câble HDMI"]
quantites = [12, 0, 3, 40]
print("En rupture :", ruptures(articles, quantites))
print("À recommander :", nb_a_recommander(quantites, 5))
print("Total en stock :", total_stock(quantites))
En rupture : ['souris']
À recommander : 2
Total en stock : 55
La souris (0) et l'écran (3) sont sous le seuil de 5 : 2 articles à recommander. Total : 12 + 0 + 3 + 40 = 55. Le chapitre 8 montre comment regrouper nom et quantité dans une seule structure.
Tu avais trouvé ?