Aller au contenu principal

Les fonctions

Ce que ce chapitre apporte 5 points
  • Définir une fonction avec def, et l'appeler.
  • Lui donner des paramètres, et récupérer un résultat avec return.
  • Ne plus confondre afficher et renvoyer, la confusion la plus coûteuse du parcours.
  • Comprendre pourquoi une variable créée dans une fonction n'existe pas au dehors.
  • Écrire une fonction complète, avec des conditions et plusieurs return.

On doit calculer une moyenne à cinq endroits du programme. Va-t-on recopier le calcul cinq fois ? Le jour où la formule change, il faudra retrouver les cinq copies, et on en oubliera une.

Une fonction est un morceau de programme qui porte un nom. On l'écrit une fois, on l'appelle autant qu'on veut. C'est l'outil qui transforme un long script illisible en briques que l'on comprend séparément, et c'est la base de tout travail à plusieurs.

Ce chapitre installe la mécanique en quatre temps : définir et appeler une fonction, lui faire entrer des valeurs, en faire sortir une, et comprendre ce qu'elle voit du reste du programme. Il suffit pour cela des variables, des conditions et des boucles. Le reste, les valeurs par défaut, plusieurs valeurs renvoyées d'un coup, global et les listes passées à une fonction, est rangé à part, en fin de chapitre.

Le chapitre suivant, bien écrire une fonction, reprend la même fonction et la rend relisable, vérifiable et réutilisable par d'autres.

Définir, puis appeler

Définition

Une fonction est un bloc d'instructions nommé. On la définit avec def, un nom, des parenthèses et deux points ; son corps est indenté. On l'appelle en écrivant son nom suivi de parenthèses.

main.py
Sortie
>_ Prêt à exécuter…
Le plan n'est pas la maison
def ne fait rien s'exécuter : il dessine le plan et range la fonction sous son nom. C'est l'appel, avec les parenthèses, qui construit la maison.
Deux conséquences qu'on vérifiera en modifiant le bloc ci-dessus. Retirer les deux appels : plus rien ne s'affiche, bien que la fonction soit parfaitement définie. Écrire saluer sans parenthèses : rien ne se passe non plus, on a simplement désigné la fonction sans la lancer.
Vérification rapideon peut se reprendre

Un programme définit une fonction saluer() avec def, mais ne l'appelle nulle part. Qu'affiche-t-il ?

Les paramètres : ce qui entre

Un paramètre est une information que la fonction reçoit pour travailler dessus. On le nomme entre les parenthèses de la définition, et l'on donne sa valeur, l'argument, au moment de l'appel.

main.py
Sortie
>_ Prêt à exécuter…

Ce que « définir » et « appeler » veulent dire se voit mieux qu'il ne se raconte. La trace ci-dessous déroule un programme ligne par ligne : la ligne surlignée est celle qui vient de s'exécuter, et le panneau de droite donne l'état des variables à cet instant.

pas 1 / 9
1def saluer(qui):
2 message = "Bonjour " + qui
3 print(message)
4
5saluer("Alice")
6saluer("Bob")

Variables

Aucune variable n'existe encore.

Dérouler le programme. La ligne du def passe sans rien exécuter, et le corps ne s'allume qu'à l'appel, dans un cadre « dans saluer() » qui naît et disparaît deux fois.
À manipuler
Avancer pas à pas et regarder trois choses. Au premier pas, la ligne du def s'exécute et aucune variable n'apparaît : le plan est rangé, rien n'est construit. Au pas suivant, la ligne de l'appel s'allume et un cadre « dans saluer() » surgit, portant déjà qui : les arguments sont liés avant que la première ligne du corps ne s'exécute. message s'y ajoute au pas d'après. Au retour, ce cadre disparaît entièrement : ni qui ni message n'ont laissé la moindre trace dans les variables globales. Le second appel recrée un cadre neuf, avec une autre valeur de qui.
Vérification rapideon peut se reprendre

Quelle différence entre un paramètre et un argument ?

Le nom du paramètre ne regarde que la fonction. À l'intérieur, la donnée s'appellera nom, quoi qu'il arrive. Le nom de la variable qu'on envoie depuis l'extérieur n'a aucun rapport avec lui, et n'a pas besoin de lui ressembler.
C'est ce qui rend une fonction réutilisable : elle se moque de ce qui se passe dehors.

main.py
Sortie
>_ Prêt à exécuter…
Vérification rapideon peut se reprendre

Une fonction a un paramètre nommé valeur. Comment doit s'appeler la variable qu'on lui passe ?

Une fonction peut recevoir plusieurs paramètres : on les sépare par des virgules, et les arguments se donnent dans le même ordre. La section suivante en montre une, addition(a, b).

La valeur de retour : ce qui sort

return renvoie un résultat à celui qui a appelé la fonction, et arrête son exécution sur-le-champ.

main.py
Sortie
>_ Prêt à exécuter…

Un appel qui renvoie une valeur s'emploie partout où l'on écrirait cette valeur : dans un calcul, dans un print, et même comme argument d'un autre appel.

main.py
Sortie
>_ Prêt à exécuter…
Afficher n'est pas renvoyer
C'est la confusion la plus coûteuse du chapitre, et la plus difficile à repérer : une fonction qui print a l'air de marcher. Le résultat s'affiche, on est content. Il n'existe pourtant nulle part dans le programme, et le jour où l'on veut s'en resservir, tout casse.
print parle à l'humain qui regarde l'écran. return donne la valeur au programme.

Le bloc suivant est faux exprès : la fonction affiche au lieu de renvoyer.

main.py
Sortie
>_ Prêt à exécuter…

40 s'affiche bien, puis TypeError: unsupported operand type(s) for *: 'NoneType' and 'float'.

Une fonction sans return renvoie None, qui signifie « rien ». prix_remise vaut donc None, et multiplier « rien » par 1,2 n'a pas de sens.

Les deux versions, côte à côte dans la même trace, rendent la différence visible : la première affiche et remonte None, la seconde ne montre rien et remonte une valeur.

pas 1 / 8
1def version_print(prix):
2 print(prix - 10)
3
4def version_return(prix):
5 return prix - 10
6
7affichee = version_print(50)
8rendue = version_return(50)

Variables

Aucune variable n'existe encore.

Deux fonctions au corps presque identique. Celle qui affiche remonte None, celle qui rend remonte un nombre, et seule la seconde permet la suite du calcul.

Le tableau des variables tranche ce que la sortie ne dit pas : 40 s'affiche dans les deux cas, mais une seule des deux variables contient quelque chose d'utilisable.

À faire. Dans le bloc faux exprès, remplacer print(nouveau_prix) par return nouveau_prix, puis relancer. Le calcul aboutit, mais plus rien ne s'affiche au milieu : c'est normal, c'est l'appelant qui décide maintenant quoi faire du résultat.

Vérification rapideon peut se reprendre

Une fonction contient print(x * 2) mais aucun return. Que vaut resultat après resultat = f(5) ?

return arrête la fonction sur-le-champ. Une fonction peut donc en contenir plusieurs, un par cas, et seul le premier atteint s'exécute.

main.py
Sortie
>_ Prêt à exécuter…

La portée : une fonction est une bulle

Définition

Une variable créée dans une fonction est locale : elle naît à l'appel, meurt au retour, et n'existe pas ailleurs. Une variable définie en dehors est globale : la fonction peut la lire, mais lui affecter une valeur crée en réalité une locale du même nom.

main.py
Sortie
>_ Prêt à exécuter…

Le bloc suivant est faux exprès : il tente de lire une locale depuis l'extérieur.

main.py
Sortie
>_ Prêt à exécuter…

NameError: name 'resultat' is not defined. La variable a existé le temps de l'appel, puis a disparu. Pour la faire sortir, il n'y a qu'un moyen : return.

Pourquoi éviter de dépendre des globales
Une fonction qui lit une variable globale donne des résultats différents à des moments différents, sans qu'on voie pourquoi en lisant son code.
Une bonne fonction reçoit tout ce dont elle a besoin par ses paramètres et rend son résultat par return. On peut alors la lire seule, la tester seule, la déplacer ailleurs.
main.py
Sortie
>_ Prêt à exécuter…

Pour aller plus loin

Rien de ce qui suit n'est nécessaire pour écrire ses premières fonctions. Ce sont des outils et des pièges qu'on rencontre ensuite, une fois la mécanique installée.

Ce que sont un paramètre, un argument et une valeur de retour se dit aussi indépendamment de Python, dans le chapitre d'algorithmique sur les fonctions et leurs paramètres, et le découpage d'un programme en fonctions y a son propre chapitre, la modularité. Sur deux points, Python est moins strict que le pseudo-code de ce parcours : une fonction y lit les variables du programme principal, on l'a vu avec taux, et une liste passée en paramètre n'y est pas recopiée, si bien que la fonction peut modifier celle de l'appelant.

Les valeurs par défaut

Un paramètre peut avoir une valeur employée quand l'appel n'en fournit pas.

main.py
Sortie
>_ Prêt à exécuter…

Nommer les arguments à l'appel devient indispensable dès qu'une fonction en prend quatre ou cinq : tracer(donnees, couleur="rouge", epaisseur=2) se relit, tracer(donnees, "rouge", 2) beaucoup moins.

Renvoyer plusieurs valeurs

Une fonction peut renvoyer plusieurs choses d'un coup : on les sépare par des virgules après return, et l'on peut les récupérer séparément.

main.py
Sortie
>_ Prêt à exécuter…

Python regroupe les deux valeurs en un tuple, que la ligne d'appel dépaquette en deux noms. Les tuples sont présentés au chapitre sur les structures de données.

Modifier une variable du programme : global

Lire une globale est fragile ; lui affecter une valeur depuis une fonction est impossible sans le dire. L'affectation crée une locale, comme l'a montré la section sur la portée, et si la fonction lit la variable avant de l'affecter, Python lève une UnboundLocalError. Le mot-clé global lève cette barrière : il annonce que le nom désigne la variable du programme principal, et non une locale.

main.py
Sortie
>_ Prêt à exécuter…

Le chapitre sur MicroPython s'en sert une fois, pour simuler sur PC une broche qu'on lit bit par bit : la fonction lire() doit garder la forme de broche.value(), sans paramètre, et c'est la seule raison de ce choix. En temps normal, on s'en passe, et la version sans global ne coûte qu'un mot de plus à l'appel :

main.py
Sortie
>_ Prêt à exécuter…

La fonction ne touche plus à rien au-dehors : elle reçoit l'ancienne valeur par un paramètre et rend la nouvelle par return. En lisant compteur = compter_piece(compteur), on voit que compteur change, ce que la version avec global cachait dans le corps de la fonction.

Une liste passée à une fonction

Vérification rapideon peut se reprendre

Une fonction fait notes.append(20) sur la liste qu'on lui passe, sans return. La liste d'origine est-elle modifiée ?

Rappel du chapitre 2 : une variable est une étiquette collée sur une valeur. Quand on passe une liste à une fonction, on ne passe pas une copie : on passe l'étiquette. La fonction en colle une seconde sur la même liste.

main.py
Sortie
>_ Prêt à exécuter…
pas 1 / 10
1def ajouter_bonus(v):
2 v.append(20)
3
4def remplacer(v):
5 v = [0]
6
7notes = [12, 15]
8ajouter_bonus(notes)
9remplacer(notes)
10print(notes)

Variables

Aucune variable n'existe encore.

Deux fonctions, un même argument. Celle qui modifie la liste se voit au dehors ; celle qui réaffecte le paramètre ne se voit nulle part.
À manipuler
Suivre notes dans les variables globales pendant les deux appels. Pendant le premier, le cadre affiche v avec la même liste que notes, et l'append change les deux d'un coup, puisqu'il n'y en a qu'une. Pendant le second, v = [0] ne change que v : la ligne a recollé l'étiquette du cadre sur une autre liste, sans toucher à celle de l'appelant. Toute la difficulté de cette section tient dans l'écart entre ces deux lignes.
Une fonction peut modifier ce qu'on lui donne
La règle de la portée dit qu'une fonction ne peut pas réaffecter une variable extérieure. Elle ne dit pas qu'elle ne peut pas modifier la valeur que cette variable désigne, et c'est exactement ce qui se passe avec une liste ou un dictionnaire.
Ce n'est ni un bug ni une exception : c'est la conséquence directe de l'étiquette. Mais c'est une surprise le jour où l'on découvre que sa liste a été vidée par une fonction appelée trois lignes plus haut.
En cas de doute, travailler sur une copie : notes = notes.copy() en première ligne de la fonction, et rendre le résultat par return.
main.py
Sortie
>_ Prêt à exécuter…

Une valeur par défaut n'est calculée qu'une fois

Le piège de la liste par défaut
Les valeurs par défaut et les listes se combinent en un piège célèbre. La valeur par défaut d'un paramètre est évaluée au moment du def, pas à chaque appel. Si cette valeur est une liste, tous les appels qui n'en fournissent pas partagent la même, et celle-ci garde ce que les appels précédents y ont mis.
main.py
Sortie
>_ Prêt à exécuter…

Aucune erreur n'est levée, et c'est bien ce qui rend le piège coûteux : le bloc affiche ['pain'], puis ['pain', 'lait'], puis ['pain', 'lait', 'oeufs']. Le deuxième appel, qui ne demandait qu'un panier neuf, reçoit les courses du premier.

La parade tient en deux lignes, et c'est la forme qu'on rencontre dans tout le code professionnel : la valeur par défaut est None, et la liste se crée dans la fonction, donc à chaque appel.

main.py
Sortie
>_ Prêt à exécuter…

La règle courte : jamais de liste, de dictionnaire ni d'ensemble comme valeur par défaut. Un nombre, une chaîne ou un booléen ne posent pas ce problème, puisqu'on ne peut pas les modifier sur place.

Mettre en pratique

La mention. Écrire mention(note) qui renvoie la mention d'une note sur 20 : « Très bien » à partir de 16, « Bien » à partir de 14, « Passable » à partir de 10, sinon « Insuffisant ».

main.py
Sortie
>_ Prêt à exécuter…
Afficher la solution
main.py
Sortie
>_ Prêt à exécuter…

Deux points valent qu'on s'y arrête.

La fonction renvoie au lieu d'afficher. C'est ce qui permet d'écrire if mention(note) == "Tres bien": ailleurs dans le programme, ce qu'une version avec print interdirait.

Le dernier cas n'a pas besoin de else : si l'on est arrivé jusque-là, c'est qu'aucun return précédent ne s'est déclenché. Les deux écritures sont correctes, celle-ci est un peu plus plate à lire.

Les exercices du thème Fonctions sont rangés dans l'ordre où les faire : les neuf premiers ne demandent que ce qui précède.

Chapitre révisé en octobre 2026 · Une erreur dans ce chapitre ?

Dans la bibliothèque d'exercices