Files
training.python.beginner/documentation/04-functions.md
2025-07-04 19:26:39 +02:00

285 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Découvrir les fonctions
author: Steve Kossouho
---
# Découvrir les fonctions
----
## L'utilité des fonctions
Une fonction, c'est :
1. Minimaliste si possible,
2. Réutilisable,
3. Un gain de temps et d'espace
----
## Syntaxe de déclaration de fonctions
```{.python .numberLines}
def my_first_function():
# Affiche un texte à chaque fois qu'on l'exécute
print("Voici le code de la fonction")
my_first_function()
```
----
## Typographie des fonctions
Les noms de fonctions se choisissent, par convention, comme toute autre variable :
- Tout en minuscules (ex. `exponential`)
- Mots séparés par des underscores (ex. `get_warning_count`)
- Peuvent contenir des chiffres sauf au début (ex. `log4`)
----
## Référence _versus_ appel de fonction
Lorsque vous définissez une fonction appelée `action` :
`action` est une variable de type fonction dont vous pouvez ensuite exécuter le code associé.
- `action` est donc la référence de la fonction.
- `action()`{.python} exécute le code de `action`, et récupère son expression de retour.
----
## Valeurs de retour d'une fonction
En mathématiques, une fonction renvoie toujours une valeur.
Par exemple, on peut déclarer `Pour tout x dans les nombres réels, f(x) = x × 15`.
En Python, pour qu'une fonction qu'on déclare renvoie une valeur (transmette une valeur au code qui l'exécute)
lorsqu'on l'exécute, il faut utiliser le mot-clé `return`{.python}.
Lorsque le mot-clé est rencontré par l'interpréteur, il interrompt immédiatement l'exécution de la
fonction et l'instruction qui a appelé la fonction récupère la valeur de l'expression qui a été retournée.
```{.python .numberLines}
def multiply_by_five(value): # Fonction qui prend un argument nommé value
return value * 5
result = multiply_by_five(10) # l'expression `10 * 5` est assignée à la variable
print(result) # affiche 50
```
----
### Valeurs de retour spécifiques
Le mot-clé `return`{.python} a quelques comportements implicites :
- Il peut être utilisé tout seul, sans expression : `return`{.python}. C'est équivalent à écrire `return None`{.python}, mais moins explicite.
- Si une fonction se termine sans avoir utilisé le mot-clé `return`{.python} :
- C'est toujours une fonction valide (on l'a vu dans le premier exemple)
- Implicitement, l'interpréteur Python retourne également la valeur `None`{.python}.
. . .
En clair, cela signifie qu'une fonction en Python renvoie **toujours** une valeur qui peut être assignée
à une variable, même si la valeur `None`{.python} a souvent peu d'utilité.
----
## Passer quelques arguments aux fonctions
Déclarer une fonction simple, c'est déjà pas mal, mais en déclarer une qui dépend de un ou
plusieurs arguments reçus en entrée, c'est bien plus utile !
Python propose au moins 4 types d'arguments différents, dont les usages sont évidemment différents,
et parmi ceux-ci, deux sont absolument essentiels et nous allons les aborder :
1. Arguments positionnels
2. Arguments par défaut (avec valeur par défaut)
----
## Arguments : Positionnels
Derrière cet adjectif un peu pompeux se cache le type d'argument le plus simple à déclarer et à utiliser :
```{.python .numberLines}
def f(x, y):
# Accepte deux arguments, affiche leur valeur
# Mais ne renvoie rien d'autre que `None`
print(x, y)
f(1, 2) # exécute la fonction, donc affiche "1 2"
```
. . .
Passer des valeurs à ces arguments est **obligatoire** lorsqu'on souhaite exécuter la fonction,
et les valeurs sont passées aux arguments dans le même ordre que dans la signature;
ici, à la ligne 5, `1`{.python} va dans `x`{.python} et `2`{.python} va dans `y`{.python}.
C'est de ce comportement que vient la notion de **positionnalité**.
----
## Arguments : Valeurs par défaut
```{.python .numberLines}
def f(x, alpha=15, beta=16):
# Accepte trois arguments, dont deux avec une valeur par défaut
print(x, alpha, beta)
return (x, alpha, beta) # renvoie un tuple
f(1) # ça marche, alpha vaut 15, beta vaut 16
f(46, 10, 12) # acceptable
f(99, alpha=23) # conseillé
f(99, 23) # équivalent à l'exemple du dessus
f(True, beta=23) # conseillé et obligatoire
f(True, , 23) # ceci est une erreur de syntaxe, on ne met pas deux virgules de suite en vrac
f(beta=23, x=50) # passer un arg. positionnel par son nom : déconseillé
```
. . .
Les arguments avec valeur par défaut sont facultatifs; cela signifie que si le développeur les omet
lorsqu'il exécute la fonction, Python choisira pour ces arguments leur valeur par défaut.
----
### Arguments : ordre
Malheureusement, mais c'est techniquement nécessaire, il y a un ordre imposé par l'interpréteur Python
lorsque vous déclarez les arguments acceptés par une fonction :
les arguments positionnels **doivent** être déclarés **en premier** dans la liste des arguments, s'il y en a.
. . .
Tous les arguments, positionnels (obligatoires) et par défaut, peuvent cependant être passés par leur nom,
mais ne faites jamais ça, vous induirez vos relecteurs en erreur :
```{.python .numberLines}
def f(x, alpha=15, beta=16):
return (x, alpha, beta) # renvoyer un tuple avec x, alpha et beta
# x peut être passé par son nom, non positionnellement, mais c'est déconseillé
print(f(beta=23, x=50))
```
----
## Bonus : Argument "étoile" (séquence)
Il existe un type d'argument, qui n'apparaît qu'une seule fois **maximum**, et dont le nom est précédé d'une étoile dans la signature de la fonction.
Cet argument apparaît après les arguments positionnels, et _de forte préférence_ avant les arguments avec valeur par défaut.
Lors de l'exécution de la fonction, cet argument contient toujours un tuple valide, même vide.
Également, comme cet argument contient toujours un tuple, il est généralement nommé avec un nom **au pluriel**.
```{.python .numberLines}
def stretchable(number, *words):
print(number, words) # words est toujours un tuple
stretchable(15, "word 1", "word 2", "word 3") # les 3 arguments vont dans le tuple `words`
stretchable(15, True, 3.14159) # les 2 arguments vont dans le tuple `words`
stretchable(1) # le tuple `words` sera vide
```
----
### Argument "étoile" : usage
Un argument de ce type accepte, lors de l'appel de la fonction, un nombre de valeurs arbitraire (0 ou plus), et ces valeurs sont passées comme des arguments positionnels (sans nom d'argument).
Cette technique est utilisée dans la fonction `print`{.python} pour pouvoir afficher à la suite plusieurs arguments.
----
On peut directement renseigner une liste pour ce type d'argument, en passant, lors de l'appel de fonction, une expression de liste (ou tuple) précédée par une étoile :
```{.python .numberLines}
def star_function(number, *words):
print(number, words) # words est toujours une liste
star_function(15, *["word 1", "word 2", "word 3"]) # raccourci
```
TODO: Si arg étoile suivi de défaut, passer *[], suivi d'une valeur positionnelle, cette dernière s'ajoute quand même à l'argument étoile, et donc obligation passer l'argument par défaut via son nom.
----
## Bonus : Argument "double-étoile" (dictionnaire)
Un dernier type d'argument, apparaît aussi une seule fois **maximum**, et généralement en tout dernier dans les arguments. Celui-ci s'utilise en passant des noms d'arguments qui n'existent pas ailleurs dans la signature de la fonction :
```{.python .numberLines}
def f_keywords(other=15, **kwargs):
print(kwargs) # toujours un dictionnaire
f_keywords(plop="Hello", foo=19) # vont dans le dictionnaire `kwargs`.
```
On appelle aussi cet argument l'argument "mots-clés", et s'appelle très fréquemment `kwargs`.
----
Si l'on appelle `f_keywords`{.python} avec un argument `plop`, Python regarde si un argument de ce nom existe. Si oui, la valeur de cet argument est modifiée. Si non, l'argument est ajouté comme association dans `kwargs`, de telle façon que `kwargs == {"plop": "Hello"}`{.python}.
Souvent utilisé par coquetterie, mais l'argument pourrait être généralement remplacé par le simple passage d'un dictionnaire dans un argument positionnel.
----
## Arguments : Ordre de déclaration
Si vous deviez avoir dans vos fonctions tous les types d'arguments que nous avons vus, l'ordre de leur
apparition **devrait** être le suivant :
1. `positionnel` (toujours premier)
2. `étoile` (utilisé positionnellement, toujours second)
3. `par défaut` (utilisé de préférence par nom, de préférence troisième)
4. `double-étoile` (utilisé par nom)
----
## Superbonus : Arguments spéciaux
----
### Arguments spéciaux : Slash
Depuis Python 3.8 (2019), vous pouvez contrôler l'utilisation de vos fonctions en ajoutant dans votre signature un argument
simplement nommé `/`{.python}. Il ne peut exister qu'une fois au maximum.
Lorsque vous spécifiez cet argument dans votre signature, **tous les arguments qui le précèdent ne
peuvent pas être spécifiés autrement que positionnellement** :
```{.python .numberLines}
def my_function(a, b, /, c, d):
print(a, b, c, d)
my_function(1, 2, 3, 4) # autorisé
my_function(1, 2, c=3, d=4) # autorisé
my_function(1, b=2, c=3, d=4) # impossible, b est positionnel uniquement
```
Lorsqu'un argument `/`{.python} ou `*`{.python} est présent, les arguments `*args`{.python} sont interdits. La raison est que, si un tel
argument est présent, on est automatiquement tenu de passer les arguments qui suivent par leur nom.
----
### Arguments spéciaux : Étoile
Vous pouvez contrôler l'utilisation de vos fonctions en ajoutant dans votre signature un argument
simplement nommé `*`{.python}. Il ne peut exister qu'une fois au maximum, et se trouver après l'argument
`/`{.python}.
Lorsque vous spécifiez cet argument dans votre signature, **tous les arguments qui le suivent ne
peuvent être spécifiés autrement qu'en précisant leur nom** :
```{.python .numberLines}
def my_function(a, b, c=3, *, d=4):
print(a, b, c, d)
my_function(1, 2, 3, d=4) # autorisé
my_function(1, 2, 3, 4) # impossible, d ne peut pas être utilisé positionnellement
```