Updated first chapter slides. Added new questions in the new training section.
330 lines
11 KiB
Markdown
330 lines
11 KiB
Markdown
---
|
||
title: Découvrir les fonctions
|
||
author: Steve Kossouho
|
||
---
|
||
|
||
# Découvrir les fonctions
|
||
|
||
----
|
||
|
||
## L'utilité des fonctions
|
||
|
||
Le principe d'une fonction, c'est d'être :
|
||
|
||
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_simple_function():
|
||
# Affiche un texte à chaque fois qu'on l'exécute
|
||
print("Bonjour, vous m'avez appelé ?")
|
||
print("Je suis une fonction très simple.")
|
||
|
||
|
||
my_simple_function()
|
||
```
|
||
|
||
La syntaxe permet de déclarer la fonction et dire à Python de lui associer du code.
|
||
|
||
----
|
||
|
||
## Typographie des fonctions
|
||
|
||
Les noms de fonctions se choisissent, par **convention** (suivez les conventions), 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éclarez une fonction appelée `action` :
|
||
|
||
```python {.numberLines}
|
||
def action():
|
||
pass
|
||
```
|
||
|
||
`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 `∀ x ∈ ℝ, f(x) = x × 15`{.latex}.
|
||
|
||
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 la valeur est transmise comme résultat de l'appel de la fonction.
|
||
|
||
```python {.numberLines}
|
||
def squared(value):
|
||
# Renvoyer la valeur élevée au carré
|
||
return value ** 2.0
|
||
|
||
result = squared(10) # on récupère 100
|
||
print(result)
|
||
```
|
||
|
||
----
|
||
|
||
### Valeurs de retour et cas particuliers
|
||
|
||
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 d'un ou de
|
||
plusieurs arguments reçus en entrée, c'est encore 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))
|
||
```
|
||
|
||
----
|
||
|
||
## Extra : 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 (aucune 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.
|
||
|
||
```python {.numberLines}
|
||
from sys import stdout
|
||
|
||
def custom_print(*values):
|
||
for value in values:
|
||
stdout.write(value)
|
||
stdout.write(" ")
|
||
stdout.write("\n") # Échappement pour passer à la ligne
|
||
|
||
custom_print("Hello", "et", "bienvenue !")
|
||
```
|
||
|
||
----
|
||
|
||
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 function_with_extensible_args(number, *words):
|
||
print(number, words) # words est toujours une liste
|
||
|
||
|
||
function_with_extensible_args(15, *["word 1", "word 2", "word 3"], "word 4") # raccourci
|
||
```
|
||
|
||
----
|
||
|
||
## Extra : 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}
|
||
from typing import Any
|
||
|
||
def function_with_custom_named_args(other: Any = 15, **kwargs):
|
||
print(other, kwargs) # toujours un dictionnaire
|
||
|
||
|
||
function_with_custom_named_args(plop="Hello", foo=19) # vont dans le dictionnaire `kwargs`.
|
||
function_with_custom_named_args(other="Hello", tornado=19) # tornado ira dans le dictionnaire `kwargs`.
|
||
```
|
||
|
||
On appelle souvent cet argument l'argument [mots-clés]{.naming}, et s'appelle très fréquemment `kwargs` ou
|
||
`options`.
|
||
|
||
----
|
||
|
||
### Fonctionnement de l'argument
|
||
|
||
Si l'on appelle `function_with_custom_named_args`{.python} avec un argument `plop`, Python regarde si la fonction
|
||
accepte explicitement un argument de ce nom :
|
||
|
||
- Si oui, l'argument prendra cette valeur.
|
||
- Si non, l'argument est ajouté comme association dans `kwargs`, de telle façon que `kwargs == {"plop": "Hello"}`{.python}.
|
||
|
||
```python {.numberLines}
|
||
def function_with_custom_named_args(other=15, **kwargs):
|
||
print(other, kwargs) # toujours un dictionnaire
|
||
|
||
|
||
function_with_custom_named_args(plop="Hello")
|
||
```
|
||
|
||
L'argument est de plus en plus rarement utilisé, sauf par des bibliothèques qui ont besoin d'accepter des noms
|
||
calculés dynamiquement pour fonctionner.
|
||
|
||
----
|
||
|
||
## Arguments : Ordre de déclaration
|
||
|
||
Si vous deviez avoir dans la déclaration de 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} 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 marqué `*`{.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
|
||
```
|