Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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éficeConcrètement
Ne pas se répéterLe calcul de la moyenne est écrit une fois, appelé partout
Lire facilementmoyenne(notes) se comprend sans lire le détail
Tester séparémentChaque fonction a son jeu d'essai (chapitre 5)
RéutiliserUne fonction bien écrite resservira dans un autre programme
Travailler à plusieursChacun é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 ?

FonctionProcédure
RôleCalculer et renvoyer une valeurEffectuer une action
RésultatUne valeur (ou plusieurs), récupérée par l'appelantPas de valeur renvoyée
AppelDans une expression : m ← Moyenne(t)Comme une instruction : AfficherBulletin(t)
ExemplesMoyenne, maximum, est_pair, prix_ttcAfficher 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.

text
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
python
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))
text
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 :

python
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.

python
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"]))
text
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 :

python
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)
text
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.

python
def moyenne_affiche(notes):
    print(sum(notes) / len(notes))


m = moyenne_affiche([12, 15, 9])
print("Résultat récupéré :", m)
text
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 :

ModeNotationSensExemple
EntréeELe sous-programme lit la valeur, sans la modifier pour l'appelantLe taux de TVA
SortieSLe sous-programme y écrit un résultat, que l'appelant récupèreLe quotient et le reste d'une division
Entrée-sortieE/SLe sous-programme lit la valeur et la modifieUn tableau à trier sur place
text
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.
python
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)
text
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.

python
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.

python
compteur = 0


def incrementer():
    compteur = compteur + 1   # UnboundLocalError


incrementer()
text
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 :

python
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).

python
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 :

  1. Saisir les notes.
  2. Calculer les résultats : la moyenne, la meilleure note, le nombre au-dessus de la moyenne.
  3. Afficher le bilan.

Niveau 2, chaque étape devient un sous-programme avec son interface :

Sous-programmeTypeEntréesSortie
saisir_notes(n)fonctionn, le nombre d'étudiantsla liste des notes
moyenne(notes)fonctionla listeun réel
maximum(notes)fonctionla listela plus grande note
compter_au_dessus(notes, seuil)fonctionla liste, un seuilun entier
afficher_bilan(moy, maxi, nb)procédureles trois résultatsrien (affichage)

Le programme principal se contente alors d'enchaîner les appels. Il se lit comme l'énoncé :

python
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 :

text
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 :

  1. est_pair(n), qui renvoie True si n est pair ;
  2. valeur_absolue(x), sans utiliser abs ;
  3. max2(a, b), qui renvoie le plus grand de deux nombres, puis max3(a, b, c) qui utilise max2.
Voir la réponseMasquer la réponse de l'exercice 1
python
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.

python
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ùabValeur renvoyée
Principal, avant l'appel34
Dans f, au début43
Dans f, a ← a + b73
Dans f, b ← a × 2714
Dans f, return a - b−7
Principal, après l'appel34c = −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).

  1. Calculer l'âge d'une personne à partir de son année de naissance et de l'année en cours.
  2. Afficher un menu de cinq choix.
  3. Mettre tous les noms d'une liste en majuscules, dans la liste elle-même.
  4. Calculer la somme et la moyenne d'une liste de montants.
Voir la réponseMasquer la réponse de l'exercice 3
  1. Fonction Age(E naissance : entier, E annee : entier) : entier.
  2. Procédure AfficherMenu(), sans paramètre : elle n'a rien à calculer ni à renvoyer.
  3. Procédure MettreEnMajuscules(E/S noms : tableau de chaînes) : la liste est modifiée sur place.
  4. 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 ?

python
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.

python
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 :

python
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-programmeTypeEntréesSortie
ruptures(articles, quantites)fonctionles deux listesla liste des articles à 0
nb_a_recommander(quantites, seuil)fonctionquantités, seuilun entier
total_stock(quantites)fonctionquantitésun entier
python
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))
text
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é ?

Chapitre terminé !

Fais les 6 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Tableaux et listes : les algorithmes de baseRevoir le sommaire du cours