Les fonctions en langage Python#
En programmation, une fonction est un bloc d'instructions nommé, autonome et réutilisable, conçu pour accomplir une tâche spécifique. L'utilisation des fonctions répond au principe fondamental DRY (Don't Repeat Yourself — Ne vous répétez pas) : plutôt que de copier-coller un bloc de 10 lignes à plusieurs endroits de votre code, vous le factorisez au sein d'une fonction unique.
En Python, les fonctions sont des citoyennes de première classe (first-class citizens) : elles peuvent être assignées à des variables, passées en argument d'autres fonctions et retournées comme résultats.
1. Définition et anatomie d'une fonction#
On définit une fonction à l'aide du mot-clé def, suivi de son nom, de parenthèses listant ses paramètres, et d'un deux-points : :
def calculer_perimetre_rectangle(longueur: float, largeur: float) -> float:
"""Calcule le périmètre d'un rectangle.
Args:
longueur (float): La longueur du rectangle en mètres.
largeur (float): La largeur du rectangle en mètres.
Returns:
float: Le périmètre total.
"""
perimetre = 2 * (longueur + largeur)
return perimetre
# Appel de la fonction
resultat = calculer_perimetre_rectangle(10.5, 4.0)
print(f"Perimetre : {resultat} m") # 29.0 m
[!NOTE] Le rôle de la docstring (PEP 257) :
La chaîne de caractères située immédiatement sous la signature de la fonction est sa documentation officielle (docstring). Elle est accessible à tout moment viahelp(ma_fonction)ouma_fonction.__doc__et est exploitée par les IDE pour afficher l'aide contextuelle.
2. Arguments positionnels vs Arguments nommés (Keywords)#
Lors de l'appel d'une fonction, vous pouvez transmettre les valeurs selon deux modes :
def creer_utilisateur(nom: str, role: str = "visiteur", actif: bool = True) -> dict:
return {"nom": nom, "role": role, "actif": actif}
# 1. Arguments positionnels (l'ordre compte)
u1 = creer_utilisateur("Alice", "admin", True)
# 2. Arguments nommés (l'ordre n'a plus d'importance, clarté maximale)
u2 = creer_utilisateur(role="analyste", nom="Bob", actif=False)
# 3. Utilisation des valeurs par défaut
u3 = creer_utilisateur("Charlie") # role="visiteur", actif=True
3. Le piège absolu de l'argument par défaut mutable#
Considérez cette fonction écrite innocemment :
# ATTENTION : BOGUE CLASSIQUE TRÈS DANGEREUX
def ajouter_tache(tache: str, liste_taches: list = []):
liste_taches.append(tache)
return liste_taches
print(ajouter_tache("Rédiger rapport")) # ['Rédiger rapport']
print(ajouter_tache("Envoyer email")) # ['Rédiger rapport', 'Envoyer email'] -> SURPRISE !
Pourquoi ce comportement ?#
En Python, les valeurs par défaut des fonctions ne sont pas évaluées à chaque appel, mais une seule fois lors de la compilation du fichier ! La liste [] est un objet unique partagé entre tous les appels ultérieurs.
La solution idiomatique : None comme sentinelle#
Pour tout argument par défaut dont le type est mutable (list, dict, set), utilisez toujours None par défaut :
# Approche correcte et sécurisée :
def ajouter_tache(tache: str, liste_taches: list = None) -> list:
if liste_taches is None:
liste_taches = [] # Une nouvelle liste est créée à chaque appel
liste_taches.append(tache)
return liste_taches
4. Nombre arbitraire d'arguments : *args et **kwargs#
Lorsque vous ignorez à l'avance combien d'arguments l'utilisateur va fournir :
A. Les arguments positionnels variables : *args#
L'opérateur étoile rassemble tous les arguments positionnels supplémentaires dans un tuple :
def calculer_moyenne(*nombres: float) -> float:
"""Accepte n'importe quel nombre de paramètres numériques."""
if not nombres:
return 0.0
return sum(nombres) / len(nombres)
print(calculer_moyenne(12, 14, 16)) # 14.0
print(calculer_moyenne(10, 20, 30, 40, 50)) # 30.0
B. Les arguments nommés variables : **kwargs#
Le double astérisque rassemble tous les arguments nommés supplémentaires sous forme d'un dictionnaire :
def afficher_configuration(nom_serveur: str, **options) -> None:
print(f"Configuration du serveur : {nom_serveur}")
for cle, valeur in options.items():
print(f" - {cle} = {valeur}")
afficher_configuration("Production-01", port=8080, ssl=True, timeout=30)
5. Portée des variables et règle LEGB#
Où Python recherche-t-il la valeur d'une variable lorsqu'elle est sollicitée ? Il applique la règle de résolution LEGB :
flowchart TD
L[1. Local : Variables définies à l'intérieur de la fonction] --> E
E[2. Enclosing : Variables d'une fonction englobante / closure] --> G
G[3. Global : Variables définies au niveau du module .py] --> B
B[4. Built-in : Fonctions et types natifs Python : print, len, range]
x = "Globale"
def fonction_externe():
x = "Enclosing"
def fonction_interne():
x = "Local"
print("Interne :", x) # Affiche "Local"
fonction_interne()
print("Externe :", x) # Affiche "Enclosing"
fonction_externe()
print("Module :", x) # Affiche "Globale"
6. Sommaire des leçons de la Partie 3#
- Les fonctions fondamentales (cette leçon) : signatures, passage d'arguments et portée.
- Les fonctions récursives : cas de base, pile d'appels et récursion terminale.
- Les fonctions anonymes (lambda) : fonctions à expression unique,
map,filteret tris avancés.
7. Défi d'application#
Écrivez une fonction formater_coordonnees(*coordonnees: float, separateur: str = " | ") -> str qui accepte un nombre arbitraire de coordonnées numériques flottantes et retourne une chaîne de caractères où chaque nombre est formaté avec deux décimales, séparés par la chaîne separateur.
Commentaires