Les bases sur les fonctions : cours

\(\newcommand{\ds}{\displaystyle}\) \(\newcommand{\Frac}{\ds\frac}\) \(\renewcommand{\r}{\mathbb{ R}}\) \(\newcommand{\C}{\mathbb{ C}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\z}{\mathbb{ Z}}\) \(\newcommand{\Q}{\mathbb{ Q}}\) \(\newcommand{\N}{\mathbb{ N}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\ol}{\overline}\) \(\newcommand{\abs}[1]{\left| \,{#1} \right|}\) \(\newcommand{\pv}{\;;\;}\) \(\newcommand{\ens}[1]{\left\{ {#1} \right\}}\) \(\newcommand{\mens}[1]{\setminus\left\{ {#1} \right\}}\) \(\newcommand{\Par}[1]{\left({#1}\right)}\) \(\newcommand{\pe}[1]{\left\lfloor {#1} \right\rfloor}\) \(\newcommand{\trans}[1]{\,^t\!{#1}}\)

Les bases sur les fonctions : cours

Cours

Les fonctions en Python

La notion de fonction

Considérons la transformation \(T\) suivante : étant donné un nombre \(x\), la transformation \(T\) envoie \(x\) sur \(100x\). On écrirait en mathématiques :

\(T(x)=100x.\)

Une fonction au sens de Python est souvent assez proche de la notion mathématique de fonction :

  • elle reçoit des données,
  • elle traite les données,
  • elle renvoie une valeur.

En résumé, retenir cette équation :

fonction = données + exécution + retour

Les fonctions en Python apparaissent sous deux formes, décrites ci-dessous. Le première forme a déjà été abordée, pas la deuxième.

Les fonctions built-in

Les fonctions par défaut peuvent être utilisées telles que Python les propose, par exemple la fonction max qui donne le maximum d’une séquence de nombres :

1
2
y = max(81, 12)
print(y)
3
81
  • Ligne 1 : la fonction max a juste été appelée sur les données 81 et 12 et elle a renvoyé une valeur (le plus grand des deux nombres, placé dans la variable y)

Les fonctions « custom »

Les fonctions personnalisées (custom), construites par le programmeur lui-même, pour les besoins spécifiques de son programme :

1
2
3
4
5
def f(x):
    return 100*x

y = f(42)
print(y)
6
4200
  • Lignes 1-2 : ici, f désigne une fonction. Le programmeur a défini une fonction f, très très simple, en écrivant (ligne 2) le code de ce que f va faire (f multiplie par 100 la valeur x qu’elle reçoit et « renvoie » le résultat). C’est le programmeur qui a écrit le code exécutable de la fonction.
  • Ligne 4 : le programmeur appelle (ie exécute) la fonction f qu’il a créée avec la donnée 42. La fonction f renvoie le résultat qu’elle a calculé.

Différences

La différence essentielle entre les deux types de fonctions :

  • une fonction built-in est immédiatement disponible sans que le programmeur ait eu à la définir ou la programmer,
  • une fonction custom doit être imaginée, nommée par le programmeur et le code exécutable de la fonction est écrit par le programmeur.

Une fonction typique

Examinons un exemple typique de création de fonction, que l’on va appeler \(\mathtt{f}\) et utilisons cet exemple pour illustrer les mécanismes de définition et d”appel de fonction qui sont les deux notions-clé à bien comprendre.

On donne deux notes sur 20, disons \(\mathtt{a}\) et \(\mathtt{b}\), à la fonction \(\mathtt{f}\) et \(\mathtt{f}\) nous renvoie True si la moyenne de ces deux notes est supérieure à 10 et False sinon.

Pour cela, \(\mathtt{f}\) doit calculer la moyenne

\(\mathtt{m=\frac {a+b}2}\)

et d’examiner si, oui ou non, l’assertion \(\mathtt{ m\geq 10 }\) est vraie ou pas.

Voici un code possible en Python d’une telle fonction :

fonction_typique.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def f(a, b):
    m = (a + b)/2
    ok = (m >= 10)
    return ok

resultat = f(14, 8)
print(resultat)

resultat = f(11, 8)
print(resultat)
11
12
True
False
  • Lignes 1-4 : le code de la fonction f. Ce code est ce qu’on appelle en fait la définition de la fonction f.
  • Ligne 6 : le programme demande à la fonction f de calculer s’il est vrai ou pas que le notes 14 et 8 donnent une moyenne supérieure à 10 ou pas. Ce que renvoie la fonction f (soit True, soit False ici) est placé dans la variable resultat.
  • Ligne 7 : on affiche le booléen resultat.
  • Ligne 4 : la fonction f renvoie un résultat, ici un booléen, à l’aide du mot-clé return qui signifie «  retourner  » ou, mieux, «  renvoyer  ».
  • Ligne 9-10 et 12 : on réutilise la fonction f mais pour des entrées différentes (les notes sont cette fois 11 et 8).

Sémantique d’un appel de fonction

On reprend l’essentiel du code de fonction_typique.py ci-dessus :

1
2
3
4
5
6
7
def f(a, b):
    m = (a + b)/2
    ok = (m >= 10)
    return ok

resultat = f(14, 8)
print(resultat)
8
True

Que se passe-t-il lors de l’appel f(14, 8) à la ligne 6 ? Réponse : les paramètres a et b (ligne 1) sont remplacés, dans cet ordre, par les arguments 14 et 8 dans la définition de la fonction f et le code Python de la définition de f (lignes 2 à 4) est exécuté avec les valeurs remplacées.

Autrement dit, une fois l’appel ligne 6 lancé, c’est comme si le code suivant était exécuté :

equiv_fonction_typique.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
a = 14
b = 8

m = (a + b)/2
ok = (m >= 10)

resultat = ok


print(resultat)
11
True
  • Lignes 1-7 : l’exécution de ce code est équivalente à l’exécution de la fonction f lors de l’appel f(14, 8).

Vocabulaire associé à la notion de fonction

On reprend le code de fonction_typique.py ci-dessus :

1
2
3
4
5
6
7
def f(a, b):
    m = (a + b)/2
    ok = (m >= 10)
    return ok

resultat = f(14, 8)
print(resultat)
  • Ligne 1 : la première ligne de la définition de la fonction est ce qu’on appelle l”en-tête de la fonction. Cet en-tête commence toujours par le mot-clé def.
  • Ligne 1 : l’en-tête se termine par le symbole «  deux-points  ».
  • Lignes 2-4 : la partie qui suit l’en-tête est ce qu’on appelle le corps de la fonction f. Ce corps est un bloc indenté.
  • Ligne 6 : l’expression f(14, 8) est ce qu’on appelle un appel de la fonction f. Cette expression est placée hors de la définition de fonction et est dite expression appelante.

Les valeurs que l’on passe à f lors de cet appel (ici 14 et 8) s’appellent les arguments de l’appel.

  • Ligne 1 : dans la définition de la fonction, ici les variables a et b, s’appellent les paramètres de la fonction f.
  • Ligne 4 : noter que l’objet qui suit le mot-clé return n’est pas entouré de parenthèses. Il est possible d’en placer mais ce n’est pas utile.

Les paramètres n’existent que dans la définition de la fonction. Les paramètres sont juste des noms qui servent à désigner les valeurs que la fonction va recevoir plus tard, lorsque la fonction sera appelée. Les paramètres ont donc un rôle purement formel.

Les arguments ne sont pas des variables, ce sont des valeurs, éventuellement des valeurs de variables. On dit parfois que les arguments sont des paramètres réels (autrement dit, non formels).

Le mécanisme d’appel est ce qu’on désigne parfois sous le terme de passage des arguments ou encore transmission des arguments.

Ordre d’exécution du code

On reprend le code de fonction_typique.py ci-dessus :

1
2
3
4
5
6
7
def f(a, b):
    m = (a + b)/2
    ok = (m >= 10)
    return ok

resultat = f(14, 8)
print(resultat)
  • Ligne 1 : l’interpréteur Python commence à cette ligne la lecture du code. En voyant l’en-tête de la fonction, il prend en compte la déclaration de la fonction f. Autrement dit à partir de ce moment-là, le nom f est reconnu comme celui de la fonction qui est définie à partir de la ligne 1. L’exécution du code continue ligne 6 en ignorant le corps de la fonction.
  • Lignes 2-4 : ces lignes ne sont pas exécutées car il n’y a pas eu, pour l’instant, d’appel à la fonction f.
  • Ligne 6 : l’ordre par défaut d’exécution est modifié puisque l’exécution retourne en arrière dans le code source pour exécuter la fonction f des lignes 1 à 4.
  • Ligne 7 : une fois l’exécution terminée, l’exécution du code passe de la ligne 4 à la ligne 7.

return et fin d’exécution

Un point essentiel à comprendre concernant le return d’une fonction peut être résumé ainsi :

return = GAME OVER

autrement dit, une fois l’instruction return exécutée, l’appel de la fonction est définitivement interrompu, sans aucune exception.

Illustrons avec le code suivant :

1
2
3
4
5
6
def f(x,y,z):
    v= x**2 + y**2 - z**2
    return v

t=f(4,3,5)
print(t)
7
0

Le code de f s’exécute ligne par ligne. Lorsque l’exécution arrive

à la ligne 3 :

  • l’exécution de la fonction f s’interrompt,
  • la valeur v obtenue est « renvoyée » et affectée à t.

Tout code figurant dans la définition de f après l’instruction return (ligne 3) serait ignoré lors de l’exécution, par exemple

1
2
3
4
5
6
7
def f(x,y,z):
    v= x**2 + y**2 - z**2
    return v
    v= 42

t=f(4,3,5)
print(t)
8
0
  • Ligne 4 : cette ligne est comme ignorée et n’est jamais exécutée par f.

Exercice type : Fonction aire de disque

Écrire une fonction aire_disque qui prend en paramètre un entier r et renvoie la surface du disque de rayon \(r\), définie par \(S=\pi r^2\). On prendra 3.14 comme valeur approchée de \(\pi\).

Solution

Le code ne présente pas de difficulté, il s’agit d’implémenter une simple formule. Seule la manière de présenter \(\pi\) peut poser question.

On peut toujours écrire :

1
2
3
4
def aire_disque(r):
    return 3.14 * r**2

print(aire_disque(10))
5
314.0

Il est plus lisible d’utiliser un nom de variable. Voici deux variantes :

1
2
3
4
5
6
PI = 3.14

def aire_disque(r):
    return PI * r**2

print(aire_disque(10))
 7
 8
 9
10
11
def aire_disque(r):
    PI = 3.14
    return PI * r**2

print(aire_disque(10))

Il est d’usage d’écrire des variables qui représentent des constantes en lettres capitales.

Il est aussi possible pour obtenir des calculs plus précis de demander au module math de Python de fournir une valeur approchée de \(\pi\) :

1
2
3
4
5
6
from math import pi

def aire_disque(r):
    return pi * r**2

print(aire_disque(10))
7
314.1592653589793

Exercice type : Implémenter la fonction signe

Écrire une fonction \(\mathtt{signe}\) qui évalue le signe d’un paramètre x :

\(\mathtt{signe(x)}=\begin{cases} \mathtt{1}& \text{si } \mathtt{x>0}\\ \mathtt{-1}& \text{si } \mathtt{x<0}\\\mathtt{0}& \text{si } \mathtt{x=0}\end{cases}\)

Solution

Il faut utiliser des instructions if/else :

1
2
3
4
5
6
7
8
9
def signe(x):
    if x>0:
        return 1
    else:
        if x<0:
            return -1
        else:
            return 0
print(signe(0), signe(4), signe(-7))
10
0 1 -1

Il est possible d’alléger un peu le code en se rappelant qu’une instruction return interrompt définitivement l’exécution d’une fonction :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Alternative

def signe(x):
    if x>0:
        return 1
    if x<0:
        return -1
    return 0


print(signe(0), signe(4), signe(-7))
12
0 1 -1
  • Ligne 6 : un else n’est pas utile car l’instruction return au-dessus entraîne que si le code accède à la ligne 6, c’est que forcément x>0 est faux, ce qui correspond à la situation d’un else.
  • Ligne 8 : pour la même raison, si l’exécution du code arrive à cette ligne, c’est forcément que x=0.

Alternative utilisant if/elif/else

Si la clause elif est connue, il est préférable d’écrire le code ainsi :

1
2
3
4
5
6
7
8
9
def signe(x):
    if x>0:
        return 1
    elif x<0:
        return -1
    else:
        return 0

print(signe(0), signe(4), signe(-7))

Fonction renvoyant plusieurs valeurs

A proprement parler, une fonction ne peut renvoyer qu’une seule valeur. Cependant, comme une fonction peut renvoyer un conteneur tel qu’une liste, on peut donner l’illusion de renvoyer plusieurs valeurs. Par exemple :

1
2
3
4
5
6
def f(x, y):
    return [x + y , x - y]

L = f(5, 2)
print(L)
print(L[0]+L[1])
7
8
[7, 3]
10
  • Ligne 2 : la fonction f renvoie un seul objet, à savoir une liste
  • Lignes 4 et 6 : une liste ayant plusieurs éléments, on peut avoir l’illusion que la fonction f a renvoyé deux valeurs.

Fonction sans paramètre

Il est possible définir des fonctions n’admettant pas de paramètre, comme la fonction f ci-dessous (ligne 1) :

1
2
3
4
def f():
    return 42

print(f())
5
42

Pour appeler la fonction f, on écrit (ligne 4) juste f() autrement dit, on place juste une paire de parenthèses vides.

En pratique, les fonctions sans paramètre sont beaucoup moins fréquentes que leurs analogues avec paramètres. Mais les fonctions sans paramètre servent parfois à regrouper des instructions dans une fonction pour les appeler en une seule fois, ce qui permet d’avoir un code plus structuré.

Un cas typique serait une fonction utilisée pour tester d’autres fonctions :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def f(a, b):
    m = (a + b)/2
    ok = (m >= 10)
    return ok


def tests():
    resultat = f(14, 8)
    print(resultat)
    resultat = f(0, 20)
    print(resultat)
    resultat = f(5, 6)
    print(resultat)

tests()
16
17
18
True
True
False
  • La fonction tests n’utilise aucune donnée et donc n’admet aucun paramètre.
  • La fonction tests n’admettant aucun paramètre, elle n’admet non plus aucun argument quand elle est appelée.

L’intérêt est qu’il suffit de retirer l’appel à tests pour désactiver le code qui est dans la fonction tests, ce qui n’était pas faisable facilement si le code était placé hors d’une fonction.

Autre exemple

Soit à coder une fonction lancer_un_de simulant le jet d’un dé à 6 faces : la fonction renvoie un entier au hasard entre 1 et 6. Une telle fonction ne dépend de rien et donc sera définie sans paramètre, plus précisément :

1
2
3
4
5
6
7
from random import randrange

def lancer_un_de():
   return randrange(1,7)

jet=lancer_un_de()
print(jet)
8
5
  • Lignes 3-4 : fonction sans paramètre.
  • Ligne 6 : appel sans argument de la fonction.

Absence de return

Une fonction agit sur des données et peut retourner une valeur explicitement :

avec_return.py

1
2
3
4
5
def f(x):
  return 100*x

y=f(42)
print(y)
6
4200

Cependant, une fonction ne comporte pas nécessairement une instruction return. Voici un exemple :

1
2
3
4
def g(x):
  print(100 * x)

g(42)
5
4200
  • Lignes 1-2 : la fonction g ne contient pas d’instruction return. Par contre, elle contient un appel à la fonction print qui exprime que l’action de g consiste à afficher quelque chose.
  • Ligne 4 : la fonction g est appelée mais g(42) ne renvoie rien. Noter qu’il n’y a pas d’appel à la fonction print à cette ligne.
  • Ligne 5 : cet affichage n’est pas la valeur de g(42) mais le résultat de l’action de l’appel g(42) : un affichage de la valeur de 100 * 42.

Dans une terminologie aujourd’hui un peu désuète, une fonction qui ne retourne pas de valeur est appelée une procédure.

Retour d’une fonction sans instruction return

La fonction suivante ne possède aucune instruction return explicite :

1
2
3
4
5
def g(x):
  print(100 * x)

y = g(42)
print(y)
6
7
4200
None

Toutefois, une telle fonction renvoie quelque chose : l’objet None. Le code ci-dessus le confirme : la variable y référence la valeur de retour de l’appel à fonction g et comme l’affichage le montre, cette valeur vaut None.

None est un mot-clé du langage Python et dont la signification dépend de son utilisation. Il est utilisé pour signifier l’absence de résultat, ou le fait que le résultat doit être considéré comme indéterminé.

L’exemple précédent n’a d’intérêt que pour comprendre qu’une fonction sans return renvoie néanmoins quelque chose. Toutefois, la plupart du temps, il n’y a aucun sens à afficher le retour d’une fonction qui ne renvoie rien. Dans le cas de la fonction ci-dessus, une version correcte serait :

1
2
3
4
def g(x):
  print(100 * x)

g(42)
  • Ligne 4 : on appelle g mais on n’affiche pas la valeur de retour de g.

Procédure ou pas ?

Si une fonction a pour tâche exclusive d’afficher, on utilisera une procédure autrement dit f ne renverra rien et se contentera d’afficher avec la fonction print.

Par exemple, si un programme doit déterminer si deux notes sur 20 données conduisent ou pas à une moyenne supérieure à 10, il serait possible d’écrire une fonction comme ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def f(a, b):
    m = (a + b)/2
    ok = (m >= 10)
    if ok:
        print("Moyenne >= 10")
    else:
        print("Moyenne < 10")


f(14, 8)
11
Moyenne >= 10

Toutefois, ce type d’usage doit rester rare.

L’inconvénient de la fonction f est qu’elle ne renvoie rien, elle se contente d’afficher, donc c’est utile uniquement pour un humain qui lit la zone d’affichage. Le message affiché ne peut être exploité par un autre programme Python alors que, pourtant, la fonction f a fait l’essentiel de la tâche, à savoir :

  • le calcul de la moyenne (ligne 2)
  • sa comparaison avec la note de 10 (ligne 3).

Pourtant, ces calculs demeurent inaccessibles à l”extérieur de la fonction, car la fonction ne renvoie rien.

Il est possible de remédier à cette difficulté en créant deux fonctions :

  • une fonction f qui regarde si, oui ou non, la moyenne est supérieure à 10 et renvoie cette comparaison ;
  • une procédure qui se contente d’afficher de façon interprétable pour un humain, le résultat renvoyé par f.

D’où le programme préférable :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def f(a, b):
    m = (a + b)/2
    ok = (m >= 10)
    return ok

def afficher(plus_que_10):
    if plus_que_10:
        print("Moyenne >= 10")
    else:
        print("Moyenne < 10")

resultat = f(14, 8)
afficher(resultat)
14
Moyenne >= 10
  • Ligne 1 : fonction qui renvoie le résultat de la comparaison avec 10 de la moyenne des notes a et b.
  • Ligne 6 : fonction qui affiche un message concernant un résultat (placé dans la variable plus_que_10).

Notion d’effet de bord

Soit la fonction suivante :

1
2
3
4
5
6
7
def f(a, b):
    m = (a + b)/2
    ok = (m >= 10)
    return ok

resultat = f(14, 8)
print(resultat)
8
True

Cette fonction agit de la manière suivante : elle reçoit des arguments et elle renvoie une valeur. Son action est uniquement déterminée par ce qu’elle renvoie. Toutefois, certaines fonctions ont une action qui ne se limite pas à renvoyer une valeur. L’exemple le plus simple est le cas d’un affichage :

1
2
3
4
5
6
7
def f(x):
    y=10*x
    print(y+1)
    return y

z=f(42)
print(z)
8
9
421
420

La fonction ci-dessus renvoie un objet mais accessoirement a une autre action : l’affichage d’une certaine valeur. Cet action qui n’est pas l’action de renvoyer un objet est appelé un effet de bord. En pratique, un effet de bord peut prendre des formes très variées comme

  • un affichage
  • la production d’un dessin avec matplotlib ou tout autre bibliothèque de dessin,
  • la création d’un fichier
  • la modification d’objets « extérieurs » à la fonction.

Instructions return multiples

La définition d’une fonction peut contenir des instructions return en plusieurs endroits.

Par exemple, soit la fonction \(\mathtt{f}\) qui prend un entier \(\mathtt{x}\) en paramètre et renvoie \(\mathtt{x/2}\) (la moitié de \(\mathtt{x}\)) si \(\mathtt{x}\) est pair et renvoie \(\mathtt{3x+1}\) si \(\mathtt{x}\) est impair. Par exemple \(\mathtt{f(5)=16}\) ou encore \(\mathtt{f(20)=10}\).

On peut coder cette fonction de la manière suivante :

1
2
3
4
5
6
7
8
def f(x):
    if x%2 ==0:
        return x//2
    else:
        return 3*x+1

print(f(5))
print(f(20))
 9
10
16
10

La définition de cette fonction utilise deux instructions return : la fin de l’exécution de la fonction se produira, selon les cas, suivant la première instruction return (ligne 3) ou suivant la seconde instruction return (ligne 5). Bien sûr, un appel de fonction n’exécute jamais deux instructions return l’une à la suite de l’autre puisque toute instruction return achève définitivement l’exécution d’une fonction.

La programmation « structurée »

Une certaine conception de la programmation, dite « programmation structurée » recommande de ne pas placer plusieurs instructions return dans une fonction parce que cela rendrait plus difficile la compréhension de l’exécution de la fonction puisqu’elle admet plusieurs « points de sortie ». Le code conforme aux principes de la programmation structurée serait plutôt le suivant :

1
2
3
4
5
6
7
8
9
def f(x):
    if x%2 ==0:
        y = x//2
    else:
        y = 3*x+1
    return y

print(f(5))
print(f(20))
  • La définition de f ne comprend qu’un seul return : la fonction ne peut terminer son exécution avant d’atteindre la ligne 6.