Vous commencez à écrire vos premiers programmes et ces derniers deviennent de plus en plus complexes. Vous rendre compte que vous voulez parfois effectuer plusieurs fois la même opération en différents endroits de votre code. Vous avez alors probablement été tenté de faire un copier-coller de ces parties redondantes.
Et là, malheur : vous vous rendez compte après plusieurs copier-coller que ce bout de code contient un bug. Il va alors falloir retrouver et corriger chaque utilisation de ce code dans tout votre code pour le rendre fonctionnel. Un vrai cauchemar en perspective. Mais voici venir la solution.
Les fonctions
Les fonctions sont des bouts de code d'un programme qui sont exécutés à chaque fois qu'ils sont appelés. Ainsi, par exemple, vous avez plusieurs fois besoin d'afficher une liste de choix dans votre console. Vous allez alors écrire ce code une première fois et le marquer comme fonction : on dit que l'on définit une fonction. Vous pourrez alors, à chaque fois que cela est nécessaire, appeler cette fonction. Ceci aura pour effet de déclencher le code présent dans ladite fonction et, dans notre cas, d'afficher notre superbe menu.
Que se passe-t-il si nous définissons plusieurs fonctions ? Comment les différencier ? Eh bien, on appelle une fonction par son nom. Ce dernier est défini au moment de la création de la fonction. Les noms des fonctions suivent les mêmes règles que les noms des variables. Il faut de plus éviter de nommer une variable et une fonction de la même façon. Tout comme pour les variables, donnez des noms clairs et explicites à vos fonctions. Par exemple, affiche_menu
est un nom clair décrivant le rôle de cette fonction et facile à retenir comparer à un nom plus court donné à la va-vite tel que f1
. Par convention, les noms de fonction sont écrits en minuscules et la séparation entre mots est délimité par un _
.
Revenons sur les avantages de cette méthode : votre code est maintenant plus court, car l'affichage de votre menu est défini à un endroit unique au lieu de se répéter plusieurs fois. De plus, si vous voulez modifier les choix de votre menu ou corriger un bug, il vous suffit maintenant de modifier la déclaration, aussi appeler définition, de la fonction. Ce changement se répercutera alors automatiquement sur tous vos appels à cette fonction.
Il faut également noter que rien n'empêche une fonction d'en appeler une autre. Par exemple affiche_menu
pourrait d'abord appeler une fonction efface_console
. Une fonction peut même s'appeler elle-même : c'est la récursivité.
Les fonctions en Python
Abordons maintenant l'implémentation en Python de ce concept de fonction. Nous allons continuer sur notre exemple précédent et créer une fonction affiche_menu
. La définition d'une fonction suit la structure suivante :
1 2 3 4 | # Code hors fonction def nom_fonction(): # Code de la fonction # Code hors fonction |
Détaillons un peu tout ça. def
est le mot clé indiquant à Python que l'on est en train de définir une fonction. nom_fonction
est le nom de la fonction. C'est celui que nous utiliserons pour l'apeller. Viens ensuite le code de la fonction qui sera exécuté à chaque appel. Notez bien l'indentation du code dans la fonction. La réduction d'un niveau d'indentation délimite la fin de la fonction. C'est le même principe que pour les conditions ou les boucles.
Voyons maintenant ce que pourrais donner notre exemple de fonction :
1 2 3 4 5 | def affiche_menu(): print("Menu :") print("* Action 1") print("* Action 2") # Et ainsi de suite |
Maintenant, à chaque fois que vous voulez afficher ce menu, il suffit d'utiliser :
1 | affiche_menu() |
Il est néanmoins très probable qu'après l'appel de cette fonction, vous utilisiez input
pour récupérer le choix de l'utilisateur. Cette instruction pourrait faire partie de la fonction. Cependant, le traitement de la réponse ne devrait pas se trouver dans la fonction car il se peut qu'il ne soit pas partout le même. Il serait alors pratique de pouvoir renvoyer un résultat, un peu à la manière de input
justement.
Renvoyer un résultat
Renvoyer ou retourner un résultat s'effectue à l'aide du mot-clé return
suivit de ce que vous désirez renvoyer. Quelques exemples :
1 2 3 | return True # Renverra toujours la même valeur. return var # Où var est une variable définit précédemment. La valeur renvoyée peut alors être différente. return input("Sélection: ") # On peut également renvoyer le résultat d'une autre fonction. |
Notre exemple devient alors :
1 2 3 4 5 6 | def affiche_menu(): print("Menu :") print("* Action 1") print("* Action 2") return input("Choix: ") # Renvoie le choix de l'utilisateur |
Il faut noter que return
interrompt la fonction, c'est à dire que tout code se trouvant après un return
ne sera pas exécuté.
1 2 | return False print("Non") # ne sera pas exécuté |
Cependant :
1 2 3 | if "toto" == input(): return False print("Non") # Sera exécuté si le texte tapé est différent de 'toto' |
On obtient alors :
1 | choix = affiche_menu() |
Les paramètres
Vous commencez maintenant à avoir plusieurs fonctions mais certaines d'entre elles ont des comportements très semblables. Par exemple, supposons que nous avons une fonction dire_bonjour
et une autre dire_au_revoir
. Vous voulez maintenant créer une fonction dire_une_blague
. Toutes ces fonctions se ressemblent au niveau de leur code et ne diffèrent donc que peu. Il semble alors que nous soyons à nouveau en train d'effectuer des copier-coller.
C'est alors qu'entrent en scène les paramètres. Ceux-ci permettent de passer en argument des informations variables à votre fonction pour que celle-ci puisse modifier son comportement. Voici un exemple pour y voir plus clair :
1 2 3 4 5 | def dire(texte): print('# ' + texte) dire('Bonjour') # Affiche `# Bonjour` dire('Au revoir') # Affiche `# Au revoir` |
Ainsi, nous pouvons nous débarrasser des fonctions dire_bonjour
et dire_au_revoir
et n'avoir plus qu'une seule fonction. On dit que texte
est un paramètre. Une fois dans le corps de votre fonction texte
peut être utilisé comme une variable ordinaire : vous pouvez lui appliquer des conditions, la modifier, la passer en paramètre à une autre fonction, …
Vous pouvez avoir plusieurs paramètres pour une seule fonction. Il vous suffit de les séparer par des virgules dans la déclaration de votre variable :
1 2 3 4 5 | def addition(a, b): return a + b addition(10, 5) # Renvoie 15 addition(10) |
Le dernier appel renvoie l'erreur suivante :
1 2 3 | Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: addition() missing 1 required positional argument: 'b' |
En effet, vous devez passer à votre fonction le nombre de paramètre attendu par celle-ci. C'est pourquoi le deuxième appel a retourné une erreur : nous n'avons passé qu'un seul paramètre au lieu de deux. Dans le cas de notre fonction, cela semble logique car pour faire une addition, nous avons besoin de deux valeurs. Cependant, ce comportement peut être parfois gênant. Imaginons la fonction saluer
qui prend en paramètre le nom de la personne à saluer. Il serait pratique de pouvoir l'appeler sans paramètre dans le cas où nous ne connaissons pas le nom de l'utilisateur. Nous allons voir comment réaliser une telle fonction.
Paramètres optionnels
Reprenons notre fonction :
1 2 | def saluer(nom): print('Bonjour ' + nom) |
Nous allons maintenant rendre le paramètre nom
optionnel. Pour ce faire, on utilise la syntaxe param = valeur
dans la définition de la fonction, où valeur
est la valeur par défaut du paramètre. Un exemple :
1 2 3 4 5 | def saluer(nom = 'Visiteur'): print('Bonjour ' + nom) saluer('Clem') # Affiche `Bonjour Clem` saluer() # Affiche `Bonjour Visiteur` |
Vous pouvez ajouter plusieurs paramètres optionnels, et même combiner paramètres optionnels et obligatoires. La seule condition est que tous vos paramètres obligatoires doivent se trouver au début, et tous ceux facultatifs à la fin, par exemple def fonction(a, b, c = 1)
et non def fonction(a, c = 1, b)
. On appelle signature la combinaison formée du nom de la fonction et des paramètres, optionnels ou non, attendus par celle-ci.
La portée des variables
Vous avez peut-être essayé de faire passer des variables entre une fonction et votre programme sans utiliser les arguments ou return
. Il se peut que certains comportements vous aient alors troublé. Nous allons essayer de percer ces mystères ensemble.
Commençons par un premier exemple :
1 2 3 4 5 6 | a = 42 def affichage(): print(a) affichage() # Affiche 42 |
Il se peut que vous soyez surpris du fait que malgré l'absence de paramètre, a
soit accessible dans la fonction affichage
. Pour Python, nous avons déclaré une variable a
puis notre fonction a cherché à afficher une variable nommée a
. Python a alors utilisé la variable qu'il connaissait à défaut de la trouver une en paramètre.
Continuons un peu notre exploration :
1 2 3 4 5 | def modifier(): print(a) a = 0 modifier() |
Et là, c'est le drame :
1 2 3 4 | Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in modifier UnboundLocalError: local variable 'a' referenced before assignment |
Mais quel est cette erreur ? Que se passe-t-il ?
Décryptons tout d'abord ce message d'erreur. Le type UnboundLocalError
peut se traduire par Erreur Locale D'affectation
. Le message nous informe que la variable a
a été utilisée avant d'être définie. Or la ligne d'avant affiche a
sans problème. Un mot qui pourrait passer inaperçu mais qui va cependant être la clé de ce mystère est local
.
Pour Python, le contexte local est l'intérieur de la fonction dans laquelle il se trouve. Il essaye alors de modifier une variable locale appelée a
qui n'existe pas car celle-ci a été déclarée en dehors de la fonction. Pour l'affichage, Python utilise la variable définie à l'extérieur. Python applique le principe suivant : vous avez le droit de lire des variables qui ne sont pas locales mais vous n'avez pas le droit de les modifier.
On pourrait s'attendre à ce que Python crée une nouvelle variable lors de l'affection. C'est ce qui se produira si vous enlevez la ligne print(a)
. En effet, a
n'ayant pas été définie localement, la variable a
extérieure a été importée pour pouvoir être lue, mais en lecture seule, empêchant toute modification.
Mais ce n'est pas tout :
1 2 3 4 5 6 7 8 | a = 42 def change(valeur): a = valeur print(a) # Affiche `42` change(10) print(a) # Affiche ... `42` encore ? C'est universel ? |
Tout ceci est normal et découle de ce qui a été dit au-dessus. Dans la fonction change
, Python ne nous autorise pas à modifier une variable extérieure. Etant donné qu'il n'a pas été obligé d'effectuer un import en lecture seule, le nom a
est toujours libre. Il créé alors une variable locale a
ayant pour valeur le paramètre de la fonction. J'insiste sur le locale : en effet, à la sortie de la fonction, le contexte local est détruit. Ainsi notre variable locale a = 10
a été détruite. On aurait néanmoins pu la récupérer à l'aide d'un return
.
Les variables globales
Il se peut que vous souhaitiez passer outre ce comportement de Python et modifier la valeur d'une variable extérieure. Pour ce faire, nous allons utiliser le mot-clé global
.
1 2 3 4 5 6 7 8 9 | a = 42 def change(valeur): global a a = valeur print(a) # Affiche `42` change(10) print(a) # Affiche `10` |
Vous devez utiliser ce mot-clé avant toute référence à a
dans la fonction. Quand Python rencontre l'instruction global a
, il va chercher dans le contexte extérieur une variable a
puis va l'importer dans le contexte local tout en autorisant sa modification.
Il faut néanmoins noter qu'il ne s'agit pas d'un remplacement de la combinaison paramètre/return. Les variables globales sont un bon choix dans certains cas très particuliers, mais si vous les utilisez trop sans vraiment les maitriser, vous risquez de vous retrouver avec des comportements inexpliqués. Évitez donc de les utiliser.
Les fonctions anonymes
Nous allons aborder une autre façon de définir une fonction. La syntaxe def
est vraisemblablement celle que vous utiliserez le plus mais, dans certains cas, elle peut être un peu longue et lourde. À cet effet, le mot-clé lambda
permet une définition plus courte mais avec un comportement différent.
La première particularité est que ces fonctions ne seront pas nommées, donc anonymes. Voyons un premier exemple :
1 2 | fonction = lambda a, b: a**b print(fonction(2,3)) # Affiche 8 |
Commençons par la première ligne. On définit notre fonction anonyme à l'aide de lambda
. Les paramètres se situent entre le mot-clé et :
, séparés par des virgules, comme pour les fonctions que nous connaissons. Vous pouvez même ajouter une valeur par défaut aux paramètres tout comme pour les fonctions définies avec def
.
Remarquez que nous n'avons pas besoin d'utiliser return
pour indiquer que nous souhaitons renvoyer un résultat. Le résultat de la dernière instruction de la fonction sera automatiquement renvoyé. Vous remarquerez que les instructions possibles dans ce type de fonction sont limitées. Si vous vous retrouvez bloqué par ces limitations, c'est probablement qu'un def
serait préférable, même si il existe parfois un moyen de passer outre cette limitation.
Malgré l'absence de nom, nous parvenons à utiliser cette fonction car nous l'avons stockée dans une variable. Par exemple, vous pouvez notamment passer cette fonction en paramètre à une autre. Voyons un exemple, certes simple, mais qui vous aidera peut-être à mieux visualiser l'utilité de ce type de fonction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def test(f, a, b=None): if b != None: r = f(a, b) else: r = f(a) if r: print("Test passé avec succès :)") else: print("Echec du test :(") pair = lambda a: a % 2 == 0 divise = lambda a, b: a % b == 0 test(pair, 6) test(divise, 6, 3) |
Vous pouvez également passer en paramètre des fonctions définies à l'aide de def
, ce n’est donc pas une particularité des fonctions anonymes. Celles-ci sont souvent utilisées pour représenter des fonctions mathématiques, mais ce n’est qu’un exemple.
Vous savez maintenant comment mieux organiser votre code grâce aux fonctions. Nous allons continuer notre organisation avec la notion de modules.