UKOnline

Définition et appel de fonction

Une fonction se définit avec le mot réservé def, suivi de son nom, d'une liste de paramètres (qui peut être vide), du caractère deux-points (:) et enfin d'un bloc de code représentant son corps. Une fois définie, elle peut être utilisée autant de fois qu'on le souhaite, en l'appelant.

On peut classifier les fonctions selon deux critères. Une fonction peut renvoyer une valeur ou non, au terme de son exécution, et une fonction peut admettre ou non des paramètres. On va maintenant voir comment définir et utiliser ces différents types de fonctions.

Liste de paramètres

Commençons avec un exemple d'une fonction qui ne renvoie pas de valeur, et n'admet aucun paramètre. Écrivons, par exemple, une fonction qui affiche la table de multiplication de $7$. Pour cela, on va évidemment utiliser une boucle while qui va parcourir les entiers de $1$ à $10$. La fonction se définit comme suit :

Ces lignes de code définissent donc une fonction dont le nom est table7. La fonction initialise une variable n à $1$, puis une boucle se répète tant que la condition n <= 10 est vraie. Le corps de la boucle affiche une ligne de la table de multiplication, puis incrémente la valeur de n d'une unité. Pour exécuter cette fonction, il suffit simplement d'utiliser son nom, suivi d'une parenthèse ouvrante et d'une fermante. L'instruction d'appel de fonction table7() produit donc le résultat suivant :

1 x 7 = 7
2 x 7 = 14
3 x 7 = 21
4 x 7 = 28
5 x 7 = 35
6 x 7 = 42
7 x 7 = 49
8 x 7 = 56
9 x 7 = 63
10 x 7 = 70

Un fichier Python est analysé par l'interpréteur ligne par ligne. Dès lors, un appel de fonction ne peut pas se faire avant que celle-ci n'ait été définie, sans quoi l'interpréteur génèrera une erreur vous signalant qu'il ne parvient pas à trouver la fonction demandée :

Traceback (most recent call last):
  File "program.py", line 1, in <module>
    table7()
NameError: name 'table7' is not defined

Imaginons maintenant que l'on souhaite aussi afficher la table de multiplication de $3$, mais aussi celle de $8$ et pourquoi pas celle de $42$. La manière la plus directe consiste à définir des fonctions table3, table8 et table42, mais ce n'est pas forcément la plus propre, car on va écrire plusieurs fois un code très similaire.

Fonction à un paramètre

Bien évidemment, cela n'est absolument pas pratique de devoir ainsi recopier du code sur lequel on ne fait finalement que de petits changements. Il y a essentiellement deux endroits où on doit effectuer un changement, à savoir dans l'instruction print(n, "x 7 =", n * 7) où il faut remplacer les deux 7.

La solution qu'on va suivre consiste à faire en sorte que la fonction admette un paramètre, qui indique le nombre dont on veut la table de multiplication. Un paramètre est identifié par un nom, que l'on place entre les parenthèses de la définition de fonction. Voici une fonction table qui admet un paramètre base et qui affiche sa table de multiplication :

Le corps de cette fonction est très similaire à celui de la fonction table7, les occurrences de 7 ayant été remplacées par base. Le grand avantage de cette fonction est qu'elle est beaucoup plus générique, c'est-à-dire qu'elle pourra fonctionner dans beaucoup plus de cas. Pour l'appeler, on utilise de nouveau son nom, sans oublier de fournir une valeur à son paramètre, entre parenthèses. Voici comment afficher successivement les tables de multiplication de $3$, $8$ et $42$ :

Lors de l'appel table(3), la fonction table est donc appelée et son paramètre base va se voir affecter la valeur spécifiée lors de l'appel, c'est-à-dire l'entier $3$. C'est ce qu'on appelle le passage de paramètres qui a lieu lors d'un appel de fonction. C'est comme si on avait l'instruction base = 3 au début du corps de la fonction table. On peut d'ailleurs rendre cela plus explicite en écrivant l'appel ainsi :

On reviendra plus loin dans ce chapitre sur cette notation particulière, qu'on a d'ailleurs déjà rencontrée lors d'appels de la méthode print, avec les paramètres nommés sep et end.

Fonction à plusieurs paramètres

Une fonction peut évidemment admettre plus d'un paramètre. Il suffit simplement de les séparer par des virgules, autant dans la définition de la fonction que lors de son appel. Modifions, par exemple, la fonction table afin de pouvoir choisir la première ligne à afficher, et le nombre de lignes que l'on veut en tout :

La fonction admet trois paramètres, et il faut en spécifier trois lors de son appel. Voici le résultat de l'appel table(8, 5, 2), où on voit que la première ligne commence bien à $5$ et qu'il y en a bien deux affichées :

5 x 8 = 40
6 x 8 = 48

De nouveau, on peut rendre explicite le passage des paramètres lors de l'appel de la fonction :

De manière générale, cette notation est à éviter car elle alourdit inutilement le code. Néanmoins, on verra, à la section suivante, qu'elle est nécessaire dans un cas particulier.

Valeur par défaut des paramètres

Dès lors qu'une fonction admet plusieurs paramètres, on doit également en fournir autant qu'il faut lors de son appel. Par exemple, on doit fournir trois paramètres lorsqu'on appelle notre dernière version de la fonction table. Si on tente de l'appeler comme on faisait au début, à savoir avec table(8), par exemple, on aura une erreur :

Traceback (most recent call last):
  File "program.py", line 7, in <module>
    table(8)
TypeError: table() missing 2 required positional arguments: 'start' and 'length'

Pour autoriser un tel appel, il faut que les paramètres start et length possèdent une valeur par défaut, c'est-à-dire celle qu'ils auront lors de l'appel si on n'en spécifie pas une autre. En Python, c'est simple, il suffit de déclarer ces valeurs par défaut lors de la définition de la fonction :

Le paramètre start a donc $1$ comme valeur par défaut et le paramètre length a $10$ comme valeur par défaut. On peut appeler cette fonction de plusieurs manières différentes :

  • On peut fournir une valeur pour les trois paramètres, en les spécifiant simplement entre parenthèses et en suivant l'ordre établi dans la définition de la fonction :
  • On peut ne fournir une valeur que pour le première paramètre, les deux autres recevant leur valeur par défaut :
  • On peut ne modifier que la valeur par défaut du paramètre start ou length, mais dans le deuxième cas, on doit nommer le paramètre qu'on veut modifier car on ne suit pas l'ordre de la définition de la fonction :

Notez que les paramètres pour lesquels on prévoit une valeur par défaut doivent impérativement se trouver après les paramètres sans valeur par défaut, sans quoi l'interpréteur génèrera une erreur.

Valeur de retour

Pour le moment, les fonctions qu'on est capable d'écrire admettent éventuellement des paramètres et permettent d'exécuter plusieurs instructions. Une fois la fonction exécutée, le programme continue son exécution en poursuivant juste après l'instruction d'appel de la fonction.

Une fonction peut également renvoyer une valeur qu'il est possible de récupérer lorsqu'on l'appelle. Commençons par voir un exemple d'une fonction qui calcule le produit de deux nombres. Contrairement aux fonctions précédemment vues, cette fonction va effectuer le calcul, mais n'affichera pas le résultat :

La fonction multiply admet donc deux paramètres a et b. Elle calcule ensuite leur produit qui est le résultat que doit produire la fonction. Pour signaler cela, on utilise le mot réservé return qui permet de définir la valeur de retour d'une fonction.

On peut appeler cette fonction comme on l'a fait jusqu'à présent, et donc écrire une instruction comme :

Cette instruction est valable et son exécution ne produira pas d'erreur. La fonction calcule le produit entre $7$ et $9$ puis renvoie le résultat de ce calcul. Mais on ne récupère pas cette valeur renvoyée lors de l'appel, et dès lors ce dernier est complètement inutile. Le résultat calculé par la fonction est tout simplement perdu à jamais.

Pour avoir accès à la valeur de retour, il faut la stocker dans une variable lors de l'appel, en écrivant par exemple :

L'exécution de ces deux instructions affiche 63. La première instruction appelle la fonction multiply et stocke la valeur renvoyée par cet appel dans la variable res, c'est-à-dire la valeur de l'expression qui suit l'instruction return à la fin du corps de la fonction (return a * b). Cette valeur est ensuite affichée par la fonction print.

Appel de fonction comme expression

L'appel d'une fonction qui renvoie une valeur est une expression, et on peut dès lors l'utiliser partout là où une expression est acceptée. Par exemple, on aurait pu écrire l'exemple précédent comme suit :

On pourrait aussi, par exemple, réécrire la fonction table en utilisant la fonction multiply pour calculer les différents produits :

On reviendra plus loin dans ce livre sur cette façon de programmer, à savoir en exploitant au maximum les fonctions.

Le type function

Enfin, un dernier point intéressant à savoir est qu'une fonction est une valeur, en ce sens qu'il existe un type de donnée fonction. On peut s'en rendre compte en faisant appel à la fonction type.

Par exemple, si on exécute l'instruction suivante : print(type(multiply)), on obtient le résultat qui suit :

<class 'function'>

Cette particularité est très puissante comme en témoigne l'exemple présenté au listing de la figure 1, qui permet d'afficher des tables de calcul (d'addition ou de multiplication). Le fichier functions.py contient avant tout les définitions des deux fonctions add et multiply.

Ensuite, on retrouve la définition de la fonction table, quelque peu modifiée par rapport à la précédente version. Celle-ci admet deux nouveaux paramètres qui sont un symbole (un caractère) et une opération à appliquer (une fonction). Par défaut, le symbole est un astérisque (*) et l'opération à appliquer est la fonction multiply.

Viennent enfin deux exemples d'appels à cette nouvelle version de la fonction table :

  • le premier appel affiche la table de multiplication de $4$, en commençant avec $1$ (la valeur par défaut) et en affichant $2$ lignes ;
  • le second appel affiche la « table d'addition » de $4$ en commençant également avec $1$ (la valeur par défaut), affiche $5$ lignes, et utilise le symbole + et la fonction add pour l'opération à appliquer.
Le fichier functions.py contient un programme qui définit et utilise plusieurs fonctions permettant d'afficher des tables de calcul.

L'exécution du programme du listing de la figure 1 affiche ce qui suit à l'écran. On y voit clairement les deux lignes de la table de multiplication et les cinq lignes de la « table d'addition » :

1 * 4 = 4
2 * 4 = 8
1 + 4 = 5
2 + 4 = 6
3 + 4 = 7
4 + 4 = 8
5 + 4 = 9

Variable locale et globale

Lorsqu'on travaille avec des fonctions, il faut distinguer deux sortes de variables : les locales et les globales. Une variable globale est définie pour tout le programme; elle est initialisée en dehors de toute fonction. Une variable locale est définie uniquement dans le corps d'une fonction, celle où elle a été initialisée. Pour comprendre ce qu'implique l'existence de ces deux sortes de variables, analysons le programme suivant :

Les trois premières lignes définissent une fonction tvac qui transforme un prix hors taxe en un prix toutes taxes incluses. Le seul paramètre qu'elle admet est le montant hors taxe à transformer. Les trois dernières lignes calculent ce que donnent $25$€ avec un taux de taxation de $21$%. Le résultat de l'exécution est tout simplement :

30.25

Lors de l'exécution de ce programme, il y a en fait deux variables n qui vont exister en même temps :

  • Celle initialisée à la deuxième ligne est une variable locale à la fonction tvac. Elle n'existe que dans le corps de la fonction, durant le temps où elle est exécutée et disparait ensuite de la mémoire.
  • Celle initialisée à l'avant-dernière instruction est une variable globale. Elle existe dans tout le programme, depuis son initialisation jusque la fin de l'exécution du programme.

Le concept de variable locale permet d'utiliser plusieurs variables différentes avec le même nom, pour autant qu'elles soient dans des fonctions différentes. Dans notre exemple, on a ainsi pu utiliser le nom n dans la fonction tvac, même si une variable globale de même nom existait déjà. Une conséquence immédiate est que la variable globale n n'est plus accessible dans la fonction tvac, au profit de la variable locale portant le même nom. Par contre, comme vous pouvez le voir sur l'exemple, la fonction tvac peut tout à fait accéder à la variable globale taxrate.

Portée de variable

La portée d'une variable représente les endroits du code où on peut l'utiliser, c'est-à-dire où elle existe. Pour bien comprendre la différence entre les deux sortes de variables, et cette notion de portée de variable, voyons quelques exemples additionnels.

Tentons d'abord d'accéder à une variable locale en dehors de la fonction qui l'a initialisée :

L'exécution de ce code provoquera une erreur car la variable a dont on veut imprimer la valeur n'existe tout simplement pas à cet endroit dans le code. La portée de la variable est limitée au corps de la fonction où elle est initialisée, la variable disparait de la mémoire une fois que l'appel de la fonction est terminé :

Traceback (most recent call last):
  File "program.py", line 5, in <module>
    print(a)
NameError: name 'a' is not defined

Voyons maintenant un deuxième exemple où on accède à une variable globale depuis une fonction :

La variable globale a est initialisée à la valeur $12$. Ensuite, la fonction fun est appelée, et affiche la valeur de cette variable, également disponible à cet endroit du code puisque c'est une variable globale.

Voyons maintenant un exemple où une fonction initialise une variable locale portant le même nom qu'une variable globale :

Il y a donc deux variables avec le nom a : une globale initialisée à $12$ et une locale à la fonction fun initialisée à $42$. En fonction de où on se situe dans le code, on accèdera donc à l'une ou l'autre, en se rappelant que la variable locale masque la variable globale. L'exécution du programme affiche donc :

dans fun : 42
en dehors de fun : 12

Voyons enfin un dernier exemple qui ne se comporte pas forcément comme on pourrait le croire de prime abord. Dans une fonction, on va commencer par tenter un accès à la variable globale a, pour ensuite initialiser une variable locale de même nom et y accéder :

L'exécution de ce code produit l'erreur suivante :

Traceback (most recent call last):
  File "program.py", line 7, in <module>
    fun()
  File "program.py", line 2, in fun
    print("globale :", a)
UnboundLocalError: local variable 'a' referenced before assignment

L'interpréteur Python signale que la variable locale a est utilisée avant d'avoir été initialisée. Elle existe donc, mais ne possède pas de valeur. Lorsqu'une fonction contient des variables locales, elles masquent les variables globales de même nom dans tout le corps de la fonction.

Modifier une variable globale

Comment modifier la valeur d'une variable globale depuis une fonction ? Si on utilise une instruction d'affectation, cela va créer une nouvelle variable locale de même nom. Pour signaler qu'un nom réfère à une variable globale, il suffit d'utiliser le mot réservé global :

Comme on verra plus loin, retenez néanmoins que ce n'est pas une bonne pratique d'utiliser des variables globales et qu'il faut limiter au maximum leur utilisation ainsi que celle du mot réservé global.

Instruction return

Revenons un moment sur l'instruction return qui permet donc de définir la valeur de retour d'une fonction. En plus de cet effet, elle permet également d'arrêter l'exécution de la fonction et de poursuivre l'exécution du programme après son appel. Tout code se trouvant après un return ne sera donc pas exécuté. Par exemple, la seconde instruction de la fonction suivante ne sera jamais exécutée :

Le fait que return arrête l'exécution de la fonction peut parfois être utilisé pour simplifier son code. Écrivons par exemple une fonction abs qui calcule la valeur absolue d'un nombre qu'on lui passe en paramètre. Voici une première version de cette fonction :

Lorsque la valeur de x est négative, la condition du if est satisfaite, et la première instruction return est exécutée. Ensuite, le corps de la fonction est quitté, les instructions se trouvant après le return n'étant pas exécutées. On peut dès lors simplifier le code de la fonction, en éliminant l'instruction else, inutile :

Cela permet de diminuer le niveau d'indentation d'une partie du code et de le raccourcir d'une ligne, ce qui le rend plus lisible. Une autre pratique que l'on retrouve parfois consiste à diminuer le nombre total de return dans une fonction. Cela permet notamment une analyse plus aisée, puisqu'on diminue ainsi le nombre de points de sortie :