⚙️ Le cours : les fonctions
Une fonction est un bloc d'instructions auquel on donne un nom. Elle évite de recopier du code (on l'écrit une fois, on l'appelle partout), rend un programme lisible (le nom dit ce que fait le bloc) et permet de tester chaque morceau séparément. On la voit comme une boîte noire : des entrées (les paramètres) et une sortie (la valeur renvoyée).
Définir et appeler une fonction
On définit une fonction avec def nom(parametres): suivi d'un bloc indenté. Les noms entre parenthèses dans la définition sont les paramètres ; les valeurs données lors de l'appel sont les arguments. Définir une fonction ne l'exécute pas : le corps ne s'exécute qu'à chaque appel.
def saluer(prenom):
print("Bonjour", prenom)
saluer("Ada")
saluer("Lin")
AffichageBonjour Ada
Bonjour Lin
return : renvoyer une valeur
return valeur termine immédiatement la fonction et renvoie la valeur à l'endroit de l'appel, où on peut la ranger dans une variable ou l'utiliser dans un calcul. Sans return, une fonction renvoie None. Ne confonds pas print (qui affiche à l'écran) et return (qui donne un résultat).
def carre(n):
return n * n
def afficher_carre(n):
print(n * n)
a = carre(6) + 1
b = afficher_carre(6)
print(a, b)
Une fonction peut contenir plusieurs return : le premier atteint met fin à l'exécution. C'est une façon claire de traiter les cas particuliers d'abord.
def signe(x):
if x > 0:
return "positif"
if x < 0:
return "négatif"
return "nul"
print(signe(5), signe(-2), signe(0))
Affichagepositif négatif nul
Paramètres positionnels, nommés et par défaut
À l'appel, on peut passer les arguments dans l'ordre (positionnels) ou par nom (hauteur=5), dans n'importe quel ordre. Un paramètre peut avoir une valeur par défaut : on peut alors ne pas le donner. Les paramètres avec valeur par défaut se placent après les autres.
def aire(largeur, hauteur=1):
return largeur * hauteur
print(aire(3, 4))
print(aire(3))
print(aire(hauteur=5, largeur=2))
Renvoyer plusieurs valeurs
return a, b, c renvoie en réalité un tuple. À l'appel, on peut le « déballer » dans plusieurs variables d'un coup, ou le garder en entier.
def stats(valeurs):
return min(valeurs), max(valeurs), sum(valeurs) / len(valeurs)
mini, maxi, moyenne = stats([12, 15, 9, 14])
print(mini, maxi, moyenne)
resultat = stats([1, 2, 3])
print(resultat, type(resultat))
Affichage9 15 12.5
(1, 3, 2.0) <class 'tuple'>
*args et **kwargs
Un paramètre précédé de * (par convention *args) rassemble tous les arguments positionnels en trop dans un tuple ; précédé de ** (**kwargs), il rassemble les arguments nommés dans un dictionnaire. À l'appel, l'étoile fait l'inverse : f(*liste) déplie une liste en arguments séparés.
def moyenne(*notes):
return sum(notes) / len(notes)
def afficher(**infos):
for cle, valeur in infos.items():
print(cle, "=", valeur)
print(moyenne(12, 15, 9))
afficher(nom="Ada", age=36)
valeurs = [4, 8, 12]
print(moyenne(*valeurs))
Affichage12.0
nom = Ada
age = 36
8.0
La portée des variables
Une variable créée dans une fonction est locale : elle n'existe que pendant l'appel. Une variable créée en dehors est globale : une fonction peut la lire, mais si elle lui affecte une valeur, elle crée en réalité une nouvelle variable locale qui la masque.
x = 10
def lire():
return x + 1
def masquer():
x = 99
return x
print(lire(), masquer(), x)
Pour modifier vraiment une variable globale, il faut la déclarer avec global. C'est possible, mais déconseillé : mieux vaut passer les valeurs en paramètres et récupérer le résultat avec return, ce qui rend la fonction prévisible et testable.
compteur = 0
def incrementer():
global compteur
compteur += 1
incrementer()
incrementer()
print(compteur)
Attention aux objets modifiables (listes, dictionnaires) : l'argument est une référence vers le même objet, donc la fonction peut le modifier en place, sans global ni return.
def ajouter_zero(liste):
liste.append(0)
nombres = [1, 2]
ajouter_zero(nombres)
print(nombres)
Documenter : docstring et annotations
La première ligne d'une fonction peut être un texte entre triples guillemets : la docstring. Elle décrit ce que fait la fonction ; help(fonction) ou fonction.__doc__ l'affiche. Les annotations (rayon: float, -> float) indiquent les types attendus, mais Python ne les vérifie pas : elles servent à la lecture et aux outils.
def aire_disque(rayon: float) -> float:
"""Renvoie l'aire d'un disque de rayon donné."""
return 3.14159 * rayon ** 2
print(aire_disque(2))
print(aire_disque.__doc__)
Affichage12.56636
Renvoie l'aire d'un disque de rayon donné.
Les fonctions sont des valeurs : lambda, key, map et filter
En Python, une fonction est un objet comme un autre : on peut la ranger dans une variable ou la passer en paramètre à une autre fonction. lambda x: expression crée une petite fonction anonyme, très utile avec le paramètre key de sorted et de max, ou avec map / filter.
def appliquer(f, valeurs):
return [f(v) for v in valeurs]
print(appliquer(lambda x: x * 2, [1, 2, 3]))
print(appliquer(len, ["ab", "cde"]))
eleves = [("Ada", 18), ("Lin", 15), ("Max", 17)]
print(sorted(eleves, key=lambda e: e[1], reverse=True))
print(max(eleves, key=lambda e: e[1]))
print(list(map(lambda x: x * 2, [1, 2, 3])))
print(list(filter(lambda x: x % 2 == 0, range(10))))
Affichage[2, 4, 6]
[2, 3]
[('Ada', 18), ('Max', 17), ('Lin', 15)]
('Ada', 18)
[2, 4, 6]
[0, 2, 4, 6, 8]
La récursivité
Une fonction récursive s'appelle elle-même, avec un problème plus petit. Elle a toujours besoin d'un cas de base qui s'arrête sans appel récursif, sinon elle ne se termine jamais. Python limite la profondeur des appels (environ 1000) : au-delà, c'est une RecursionError.
def factorielle(n):
if n <= 1:
return 1
return n * factorielle(n - 1)
def somme_chiffres(n):
if n < 10:
return n
return n % 10 + somme_chiffres(n // 10)
print(factorielle(5), factorielle(10))
print(somme_chiffres(2845))
Le calcul de Fibonacci s'écrit presque comme sa définition mathématique, mais cette version recalcule les mêmes valeurs un très grand nombre de fois (son coût est exponentiel) : elle ne convient que pour de petits n.
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print([fibonacci(i) for i in range(10)])
Affichage[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Tester une fonction avec assert
assert condition ne fait rien si la condition est vraie et lève une AssertionError sinon. Quelques assert écrits à côté d'une fonction prouvent qu'elle donne les bons résultats sur des cas connus, et préviennent si on la casse plus tard.
def est_pair(n):
return n % 2 == 0
assert est_pair(4)
assert not est_pair(7)
assert est_pair(0)
print("tests réussis")