Chp E. Les fonctions
1. Le principe
Dans un même programme, une opération, ou une séquence d'opérations peut intervenir à plusieurs reprises. Dans ce cas, il est intéressant de définir une fonction qui exécute ce bloc d'instructions. Découper un programme en fonctions élémentaires permet d'améliorer et de faciliter sa lisibilité. De même lorsqu'on résout un problème, il est souvent intéressant de le scinder en plusieurs petits problèmes (on pensera à l'adage diviser pour régner !). Là encore la notion de fonction prend tout son sens. Nous allons voir dans ce chapitre comment cela s'utilise.
Les fonctions représentent une brique fondamentale de la programmation. Il est important de bien les maîtriser.
Un premier exemple inspiré des mathématiques
On va ici définir une fonction (comme en mathématiques) dont le rôle est de renvoyer le carré du nombre reçu :
def f(x):
return x*x
print(f"L'image par f de {2} est {f(2)}")
Ce programme affichera `L'image par f de 2 est 2
2. Implémentation en python
On peut imager la notion de fonction en python comme une extension du langage... on enrichit le vocabulaire du langage de base par de nouveaux mots. Une fonction en informatique est assez proche de la notion de fonction mathématique : elle reçoit des arguments (éventuellement aucun), elle les traite et elle renvoie une valeur (éventuellement aucune).
À retenir
On retiendra que l'utilisation des fonctions personnelles en python s'effectue en deux temps.
-
D'abord on commence par définir la fonction. Pour cela on utilise les mots clés
def
etreturn
. À ce stade, le code contenu dans la fonction n'a pas encore été exécuté. -
Pour utiliser, ou appeler la fonction on écrit son nom, suivi de parenthèses contenant éventuellement des arguments.
La syntaxe en python pour définir une fonction est la suivante :
def nom_de_la_fonction(liste de paramètres) :
""" la documentation... (docstring)"""
Les instructions...
return valeur de retour
Voyons sur un exemple détaillé les différents point à retenir pour définir une fonction :
# déclaration
def fonction_carree(x : float) -> float :
""" cette fonction reçoit un flottant et renvoie
le carré de ce flottant
Ex : fonction_carree(2) renvoie 4
"""
calcul = x*x
return calcul
# utilisation
print(fonction_carree(2))
On notera :
- présence du mot clé
def
- ensuite le nom de la fonction (simple mais évocateur, sans accents, sans espaces)
- ensuite des parenthèses contenant en principe des paramètres (mais les parenthèses peuvent être vides s'il n'y a pas de paramètres)
- le ou les paramètres, séparés par des virgules. On notera dans l'exemple que l'on a fait suivre le paramètre du symbole ':' et de son type, ceci est facultatif mais c'est une bonne habitude (facilite la lecture entre autre...)
- le type de la valeur de retour précédé des symboles -> (ceci est aussi facultatif, mais c'est une bonne habitude...)
- le symbole ":" (impératif, à ne pas oublier)
- Le contenu de la fonction est indenté.
- On commence par un texte descriptif aussi détaillé que possible entouré par des triples guillemets (on parle de la docstring)
- Ensuite on place les instructions à effectuer
- On termine avec le mot clé
return
suivi des valeurs de retour.
Application : découpage d'un problème en sous problèmes
Faire le TD E1 : on verra comment découper un problème en petits sous-problèmes à l'aide de la notion de fonction. Le problème abordé est la persistance multiplicative des nombres.
3. Variables locales, variables globales
!!!! Attention, ce point est central et très important à comprendre !!!!
Le principe de base est que toute variable créée à l'intérieur d'une fonction est locale, c'est à dire n'est pas visible depuis l'extérieur de la fonction et heureusement !...
Illustration des variables locales vs globales
def g(): # on définit une fonction g
b = 2 # on définit une variable locale b à l'intérieur
b = b + 10
print(f"à l'intérieur de g : a={a} et b={b}")
a, b = 1, 1 # on définit a et b à l'extérieur
g() # on appelle la fonction g
print("à l'extérieur de g : a={a} et b={b}")
L'exécution de ce code va afficher :
à l'intérieur de g : a = 1 et b = 12
à l'extérieur de g : a = 1 et b = 1
On constate :
-
que la variable
b
à l'intérieur de la fonctiong
est différente de la variableb
à l'extérieur. -
que la variable
a
définie à l'extérieur de la fonctiong
est tout de même visible depuis l'intérieur de la fonctiong
mais en lecture uniquement (on ne peut pas la modifier). Si on tente de la modifier, une erreur est levée (UnboundLocalError), car la variable serait locale et globale à la fois, ce qui est problématique (voir ci-dessous une petite subtilité)
Une petite subtilité : une variable ne peut pas être globale et locale à la fois dans une fonction. Heureusement, Python va lever une erreur UnboundLocalError
car sinon un code deviendrait vite illisible. Il faut choisir ! Par exemple le code suivant ne fonctionne pas:
def f(): # on définit une fonction f
a = a + 1 # ici a est locale (à gauche du =) et globale (à dte) !!
a = 2 # a est une variable globale
f() # on appelle la fonction f
L'exécution va donner :
Traceback (most recent call last):
File "<input>", line 5, in <module>
File "<input>", line 2, in f
UnboundLocalError: cannot access local variable 'a' where it is
not associated with a value
Question : comment faire si l'on veut effectivement modifier une variable globale a
à l'aide d'une fonction ?
Réponse
On peut procéder en utilisant les paramètres et la valeur de retour :
def incrementer(n : int) -> int :
""" cette fonction reçoit une nombre entier
et renvoie cet entier augmenté de 1
"""
return n+1
a = 2
a = incrementer(a)
# la variable a contient maintenant la valeur 3
Noter que le nom de la variable reçue par la fonction n
n'a aucunement besoin d'être le même que celui de la variable globale a
et heureusement !
3. Passage des arguments
Attention
Il y a un point important qu'il faut prendre en considération dans python : le passage des arguments aux fonctions se fait par référence, c'est à dire que l'objet passé en argument est partagé avec la fonction (on ne passe que son adresse mémoire) et qu'il risque d'être modifié si on n'en fait pas une copie.
Voyons un exemple pour illustrer cela :
def rallonge(lst : list) -> list :
""" cette fonction reçoit une liste et y ajoute un élément.
Attention la liste initiale est modifiée"""
lst.append("valeur ajoutée")
# absence de return, la fonction ne renvoie rien...
my_list = ['a','b', 'c']
print(my_list)
rallonge(my_list)
print(my_list)
# sortie :
#['a', 'b', 'c']
#['a', 'b', 'c', 'valeur ajoutée']
On constate qu'après l'appel de la fonction rallonge
la liste my_list
définie de façon globale a été modifiée ! En effet les mots lst
et my_list
désignent le même objet (la liste ['a', 'b', 'c']
) puisque les arguments sont passés par référence.
Il est déconseillé d'utiliser cette fonctionnalité pour modifier un objet, car cela nuit fortement à la lisibilité du programme. En pratique on préférera faire une copie de la liste en début de fonction, puis renvoyer avec le mot return
la nouvelle liste modifiée. On verra cela plus en détails dans le TD E2.
Une petite subtilité (encore !) : le comportement décrit précédemment n'opère que si l'argument est dit mutable (comme une liste ou un dictionnaire). Sinon, quand l'argument est dit immuable (c'est le cas des nombres et des chaînes de caractères)une copie est automatiquement créée par la fonction, et par conséquent la variable ne sera pas modifiée. Voir encore le TD E2 pour des détails.
4. Les TD E
-
Le TD E1 : introduction aux fonctions
-
Le TD E2 : fonctions et passages des arguments
-
Le TD E3 : fonctions, exercices