Créer ses fonctions
Une fonction est un bloc de code exécutable. On peut lui associer un nom et exécuter ce code juste en l'appelant : ceci permet de ne pas copier/coller des lignes code identiques à différents endroit du programme.
Il n'est jamais bon de copier/coller un bout de programme qui se répète plusieurs fois (corriger un problème dans ce bout de code reviendrait à le corriger autant de fois qu'il a été dupliqué... si on se rappelle des endroits où il l'a été). Il est de plus souvent utile de séparer les éléments logiques d'un programme en unités autonomes, ceci rend le programme plus facile à relire.
Définition d'une fonction
Une fonction est un bloc auquel on donne un nom (le nom de la fonction) qui peut être exécuté lorsqu'on l'invoque par son nom.
def <nom>(paramètre 1, paramètre 2, ..., paramètre n):
instruction 1
instruction 2
...
instruction n
return <objet>
Les paramètres et la dernière la dernière ligne avec return
sont optionnelles.
La partie de programme suivant définit une fonction :
def bonjour():
print("Salutations")
La première ligne est la définition du bloc fonction. Il contient :
- un mot clé spécial précisant que l'on s'apprête à définir une fonction:
def
- le nom de la fonction. Ici
bonjour
- des parenthèses qui pourront contenir des paramètres (on verra ça plus tard)
- le
:
qui indique que la ligne d'après va commencer le bloc proprement dit
Ensuite vient le bloc fonction en lui-même qui ne contient ici qu'une seule ligne.
Si on exécute le bloc précédent, il ne se passe rien. En effet on n'a fait que définir la fonction. Pour l'utiliser, ajoutez bonjour()
à la suite du bloc.
Une fonction s'utilise toujours en faisant suivre son nom d'une parenthèse contenant ses paramètres séparés par une virgule (notre fonction n'a pour l'instant pas de paramètres). Donner juste son nom ne suffit pas à l'invoquer.
Nom d'une fonction
Un nom de fonction est variable comme une autre. Elle est affectée dans l'espace de nom du bloc dans lequel elle est défini.
Dans le code suivant, exécuté dans un interpréteur on regarde le type d'un nom associé à une fonction :
>>> def bonjour():
... print("Salutations")
...
>>> type(bonjour)
<class 'function'>
On peut aussi associer la fonction à une autre variable comme on le ferait avec n'importe quel autre objet. Dans l'exemple suivant on associe la fonction à une autre variable, x
:
>>> def bonjour():
... print("Salutations")
...
>>> x = bonjour
>>> x()
Salutations
En python, lorsque l'on exécute une fonction on dit qu'on l'appelle. Appeler une variable est alors le fait de mettre des ()
après son nom.
Si cela produit une erreur ce n'était pas une fonction. Regardez l'exemple ci-après, exécutable dans un interpréteur. On tente d'appeler un entier et python nous indique que ce n'est pas possible :
>>> n = 3
>>> n()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
Enfin, en python être une fonction n'est rien d'autre que d'être un objet appelable. Savoir si un objet est appelable ou pas se fait par la fonction callable
:
>>> def bonjour():
... print("Salutations")
...
>>> callable(bonjour)
True
>>> callable(1)
False
>>> callable("ee")
False
Les fonctions ne sont pas les seules objets appelables, les types le sont également : le résultat de l'appel du type list
(c'est à dire list()
) crée une liste vide.
Il en existe de nombreux autres, python étant friand de ce genre d'opérations.
Paramètres d'une fonction
TBD parler d'espace de nom
def plus_moins(nombre):
if nombre > 42:
print("Supérieur à 42")
else:
print("Inférieur à 42")
Cette fonction nécessite donc un paramètre pour être invoquée. Testez alors plus_moins(17)
.
La variable nombre sera associée à l'objet entier de valeur 17 dans la fonction. La variable nombre n'existe que dans la fonction.
Python, à chaque exécution d'une fonction crée un espace de nom pour elle. Cet espace de nom sera détruit lorsque la fonction aura fini d'être exécutée. Une fois cet espace de nom crée, il associe le nom du paramètre à l'objet passé en paramètre.
Les paramètres d'une fonction sont des noms de variables qui ne seront connus qu'à l'intérieur de la fonction. À l'exécution de la fonction, le nom de chaque paramètre est associé à l'objet correspondant.
Regardons le bout de code suivant, qui utilise la fonction plus_moins
définie précédemment :
x = 12
plus_moins(x)
Lorsque python exécute la deuxième du code précédent il va :
- créer un espace de nom pour la fonction
- regarder les objets passés en paramètre. Ici c'est l'objet associé au nom
x
. Python cherche l'objet, c'est un entier valant 12. - python associe chaque objet à son nom dans l'espace de nom de la fonction : ici l'entier qui vaut 12 sera appelé
nombre
dans la fonction (le nom du paramètre dans la définition de la fonction). - python exécute la fonction.
- à la fin de la fonction, l'espace de nom de la fonction est détruit (on ne détruit que les noms, pas les objets associés).
Créez et testez une fonction nommée cube
qui prend un entier en paramètre et affiche cet élément au cube.
solution
solution
def cube(x):
print(x ** 3)
cube(2)
Créez et testez une fonction nommée puissance
qui prend deux entiers en paramètre et affiche à l'écran le premier paramètre élevé à la puissance du second paramètre.
solution
solution
def puissance(x, y):
print(x ** y)
puissance(2, 3)
puissance(3, 2)
Paramètres par défaut
def plus_moins(nombre, seuil=42):
if nombre > seuil:
print("Supérieur à", seuil)
else:
print("Inférieur à", seuil)
On peut alors utiliser la fonction comme précédemment, plus_moins(20)
, ou en utilisant le paramètre seuil plus_moins(20, seuil=10)
.
Comme le paramètre par défaut est le deuxième on peut aussi l'utiliser sans le nommer : plus_moins(20, 10)
Créez et testez une fonction nommée puissance
qui prend deux entiers en paramètre et affiche le premier paramètre élevé à la puissance du second paramètre. Le second paramètre vaut 2 par défaut.
solution
solution
def puissance(x, y=2):
print(x ** y)
Retour d'une fonction
Toute fonction peut rendre une valeur. On utilise le mot-clef return
suivi de la valeur à rendre pour cela. Le fonction suivante rend le double de la valeur de l'objet passé en paramètre:
def double(valeur):
x = valeur * 2
return x
Il ne sert à rien de mettre des instructions après une instruction return
car dès qu'une fonction exécute cette instruction, elle s'arrête en rendant l'objet en paramètre. Le retour d'une fonction est pratique pour calculer des choses et peut ainsi être affecté à une variable.
Le résultat de la cellule devrait être : 42.
Le code précédent exécute la fonction de nom double
avec comme paramètre un entier de valeur 21
. La fonction commence par associer à une variable nommée valeur
l'objet passé en paramètre (ici un entier de valeur 21
), puis crée une variable de nom x
à laquelle est associée un entier de valeur 42
et enfin se termine en retournant comme valeur l'objet de nom x
. Les variables valeur
et x
définies à l'intérieur de la fonction sont ensuite effacées (pas les objets, seulement les noms).
Cette valeur retournée est utilisée par la commande print
pour être affichée à l'écran.
Les noms de paramètres d'une fonction et les variables déclarée à l'intérieur de la fonction n'existent qu'à l'intérieur de celle-ci. En dehors de ce blocs, ces variables n'existent plus.
Créez et testez une fonction nommée puissance
qui prend deux entiers en paramètre et rend le premier paramètre élevé à la puissance du second paramètre. Le second paramètre vaut 2 par défaut.
solution
solution
def puissance(x, y=2):
return x ** y
Fonction en paramètre
Une fonction étant un objet comme un autre, elle peut très bien être utilisée comme paramètre :
def calcul(fct, z):
return fct(2, 17) + z
Le premier paramètre de la fonction calcul
est appelé avec deux paramètres et son résultat est additionné au second paramètre.
La ligne suivante est alors du python correct :
print(calcul(produit, 8))
Si on a au préalable définit produit
comme une fonction à deux paramètres, comme par exemple :
def produit(x, y):
return x * y
Exécutez le code précédent et expliquer son fonctionnement
solution
solution
Le code final doit définir produit avant son utilisation. Il faut par exemple avoir le code :
def calcul(fct, z):
return fct(2, 17) + z
def produit(x, y):
return x * y
print(calcul(produit, 8))
Notez que lors de la définition de la fonction calcul
, la variable fct
n'est qu'un paramètre anonyme. Ce paramètre ne doit être défini que lors de son appel, à la ligne 7.
La ligne 7 fonctionne alors comme suit :
- l'objet de type fonction de nom
produit
est passé en paramètre de la fonctioncalcul
- le retour de l'appel
calcul(produit, 8)
est égal à $8 + (2 * 17) = 42$ puisquefct
est la fonctionproduit
. - son retour (42) est ensuite affiché à l'écran grâce à la fonction
print
Lambda
Les lambda sont ue façon d'écrire rapidement une fonction avec une unique instruction.
Les deux codes suivant sont identiques :
double = lambda x: 2 * x
et :
def double(x):
return 2 * x
Le principal intérêt de ces fonction est d'être utilisée comme paramètre d'autres fonction.
Par exemple avec le paramètre key
de la méthode de liste sort
. Considérons la liste l
:
l = [["au revoir", 2], ["bonjour", 1]]
Si on cherche à trier l
, la liste sera triée en comparant le 1er élément de chaque liste :
l.sort()
print(l) # donnera [['au revoir', 2], ['bonjour', 1]]
Si l'on veut trier sur le deuxième élément de chaque liste, on utilise le paramètre key
qui est une fonction. Les éléments $x$ de la liste seront triés selon $key(x)$ plutôt que $x$ :
def second(x):
return x[1]
l.sort(key=second)
print(l) # donnera [['bonjour', 1], ['au revoir', 2]]
Que donnerait le tri si la fonction second
avait été définie comme ceci :
def second(x):
return 1 / x[1]
solution
solution
def second(x):
return 1 / x[1]
l = [["au revoir", 2], ["bonjour", 1]]
l.sort(key=second)
print(l)
Utiliser une fonction lambda permet de raccourcir le code précédent tout en le gardant très clair :
l = [["au revoir", 2], ["bonjour", 1]]
l.sort(key=lambda x: x[1])
print(l) # donnera [['bonjour', 1], ['au revoir', 2]]
Annotations de type
Les annotations de types permettent de renseigner le type des entrées et de la sortie d'une fonction python. Il n'est pas nécessaire de le faire, mais si vous avez besoin d'expliciter une signature de fonction comme on le ferait dans un langage compilé comme java, vous pouvez le faire en ajoutant :
- son type à chaque paramètre (précédé d'un
:
) - le type de sortie (précédé d'un
->
)
Par exemple, la fonction suivante permet de savoir si un élément est dans une liste :
def recherche(t, x):
for e in t:
if e == x:
return True
return False
Si l'on veut restreindre cette fonctions aux listes d'entier on pourra écrire :
def recherche(t: [int], x: int) -> bool
for e in t:
if e == x:
return True
return False
La plupart du temps, pour de petits programme, ce genre de précision n'est pas importante. Elle ne devient cruciale que lorsque la base de code grossit et que spécifier les types d'entrée évite les bug.
Mais alors, il est de toute façon plus pertinent d'écrire dans un autre langage que python... Plus adapté au développement de grosses applications comme le java ou encore le rust.