Aller au contenu principal

Les fonctions

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 oublieras 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 qu'on va apprendre

  • 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 chapitre.
  • Comprendre pourquoi une fonction ne voit pas le reste du programme, et pourquoi elle peut quand même modifier une liste.
  • Écrire des fonctions qu'on relit sans effort.

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

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…
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…

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

À faire. Remplace print(nouveau_prix) par return nouveau_prix et relance. 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.

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

Renvoyer plusieurs valeurs

Une fonction peut renvoyer plusieurs choses d'un coup. Python les regroupe, et l'on peut les récupérer séparément.

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

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.

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…

Le cas des listes

Souvenons-nous du chapitre 2 : une variable est une étiquette collée sur une valeur. Quand on passe une liste à une fonction, on ne passes 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…
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, travaille sur une copie : notes = notes.copy() en première ligne de la fonction, et rends le résultat par return.
main.py
Sortie
>_ Prêt à exécuter…

Les règles d'or d'une bonne fonction

Une seule chose. Si on ne peut pas dire ce que fait la fonction en une phrase sans « et », c'est qu'il y en a deux.

Un nom qui dit le résultat. calculer_moyenne vaut mieux que traiter. Une fonction qui renvoie un booléen se nomme volontiers est_pair, contient, peut_ouvrir.

Tout par les paramètres. Ce dont elle a besoin entre par la porte, pas par une globale.

Un résultat par return. L'affichage appartient à celui qui appelle, pas à elle. Une fonction qui calcule et affiche est deux fois moins réutilisable.

Courte. Au-delà d'une vingtaine de lignes, il y a presque toujours une deuxième fonction cachée dedans.

Documenter avec une docstring

Un texte entre triples guillemets, juste après le def, dit ce que fait la fonction. C'est ce que renvoie help(), et ce que l'éditeur affichera quand on la réutiliseras dans six mois.

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

Une bonne docstring dit ce que la fonction renvoie, pas comment elle s'y prend : le comment est déjà dans le code, juste en dessous.

Vérification rapideon peut se reprendre

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

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

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

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

5.Que fait return au milieu d'une fonction ?

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

Synthèse

  • def définit, les parenthèses appellent. Définir sans appeler ne fait rien.
  • Les paramètres sont l'entrée, return la sortie.
  • Le nom d'un paramètre est local : il n'a aucun lien avec la variable passée à l'appel.
  • Afficher n'est pas renvoyer. Sans return, une fonction renvoie None.
  • return arrête la fonction sur-le-champ.
  • Une variable créée dans une fonction est locale et disparaît au retour.
  • Une fonction peut modifier une liste qu'on lui passe : c'est la même liste, pas une copie.
  • Une bonne fonction fait une seule chose, reçoit tout par ses paramètres, et rend par return.

Mettre en pratique