Aller au contenu principal

Fonctions

Progression

#Fonctions

Les fonctions constituent un pilier fondamental de la programmation structurée. Elles permettent d'isoler une logique réutilisable et testable, de réduire la duplication de code et de documenter clairement le contrat entre entrées et sorties. Une fonction bien conçue encapsule un comportement spécifique et communique son intention à travers son nom, ses paramètres, sa valeur de retour, ses annotations de type et sa documentation.

Objectifs d'apprentissage

L'objectif est d'apprendre à déclarer des fonctions claires en utilisant des noms explicites, des docstrings informatives et des annotations de type précises. Vous maîtriserez également les différents types de paramètres, qu'ils soient positionnels, nommés, avec valeurs par défaut ou variadiques (*args, **kwargs). Enfin, vous comprendrez les mécanismes de liaison des noms lors du passage d'arguments et apprendrez à éviter les pièges courants liés aux valeurs par défaut mutables.

pythonpython
1def area(w: float, h: float) -> float:2    """Retourne l'aire d'un rectangle."""3    return w * h4 5print(area(3, 4))

Les arguments nommés et les valeurs par défaut rendent les fonctions plus flexibles et expressives. Une valeur par défaut permet d'omettre certains arguments lors de l'appel, tout en gardant la possibilité de les spécifier explicitement quand nécessaire :

pythonpython
1def power(x, exp=2):2    return x ** exp3 4print(power(3), power(2, exp=3))

Python offre une grande flexibilité dans la définition des paramètres. Les paramètres peuvent être positionnels, nommés, ou réservés aux mots-clés uniquement (après l'astérisque *). Les paramètres variadiques *args et **kwargs permettent respectivement d'accepter un nombre variable d'arguments positionnels et nommés :

pythonpython
1def normalize(values: list[float], *, eps: float = 1e-9) -> list[float]:2    m, M = min(values), max(values)3    span = max(eps, M - m)4    return [(v - m) / span for v in values]5 6def summarize(title: str, *points: str, **meta):7    print(f"# {title}")8    for p in points:9        print("-", p)10    if meta:11        print("(meta)", meta)12 13summarize("Plan", "a", "b", owner="alice")

Il est généralement préférable de privilégier le passage explicite de paramètres plutôt que l'utilisation de variables globales. Cette approche rend les fonctions plus prévisibles, plus faciles à tester et moins sujettes aux effets de bord inattendus.

#Récursion

La récursion est une technique où une fonction s'appelle elle-même pour résoudre un problème en le décomposant en sous-problèmes plus simples. C'est une approche élégante pour de nombreux algorithmes, notamment ceux qui manipulent des structures récursives comme les arbres ou qui suivent une définition mathématique récursive :

pythonpython
1def fact(n: int) -> int:2    return 1 if n <= 1 else n * fact(n-1)3 4print(fact(5))

#Visualiser la pile d'appels

main
Sommet
Pile initialisée avec 1 éléments
Étape 1 / 1 | Taille: 1

#Playground

Chargement de l’éditeur...

#Exercices

Ces exercices vous permettront de pratiquer la conception et l'implémentation de fonctions :

Commencez par implémenter la fonction pgcd(a, b) pour calculer le plus grand commun diviseur de deux nombres en utilisant l'algorithme d'Euclide. Ensuite, écrivez une version optimisée de la suite de Fibonacci fib(n) intégrant la mémoïsation via un dictionnaire pour éviter les calculs redondants. Poursuivez en créant une fonction capable de normaliser une liste de nombres afin de les ramener dans l'intervalle [0, 1]. Enfin, pour aller plus loin, implémentez un décorateur permettant de mesurer et d'afficher le temps d'exécution d'une fonction quelconque.

Une bonne pratique consiste à commencer par écrire des tests avec des exemples d'entrées et de sorties attendues. Une fonction bien conçue prend ses données en paramètres plutôt que de lire un état global, et retourne le résultat plutôt que de l'imprimer directement, ce qui la rend plus réutilisable et testable.

#Solutions (exemples)

#Pièges fréquents

Un piège classique en Python concerne l'utilisation de valeurs par défaut mutables. Lorsqu'une valeur par défaut est mutable (comme une liste ou un dictionnaire), elle est créée une seule fois lors de la définition de la fonction, et non à chaque appel. Cela peut mener à des comportements surprenants où la même instance est partagée entre tous les appels :

pythonpython
1def append_bad(item, bucket=[]):  # Anti‑pattern2    bucket.append(item)3    return bucket4 5print(append_bad(1))6print(append_bad(2))  # Surprise: partage le même bucket !7 8def append_ok(item, bucket=None):9    if bucket is None:10        bucket = []11    bucket.append(item)12    return bucket

Les annotations de type et les docstrings rendent votre code plus maintenable et auto-documenté. Les annotations aident les outils d'analyse statique à détecter des erreurs potentielles, tandis que les docstrings servent de documentation intégrée au code :

pythonpython
1def area(w: float, h: float) -> float:2    """Retourne l'aire d'un rectangle.3 4    Args:5        w: largeur (>= 0)6        h: hauteur (>= 0)7    Returns:8        Aire (w*h).9    """10    return w * h
Alias involontaires

Faire b = a ne copie pas une liste, cela crée un deuxième nom pour le même objet. Utilisez a.copy() ou list(a) si vous avez besoin d'une copie superficielle.

Docstrings et exemples

Une docstring courte avec un exemple d'usage (doctest) sert de documentation et de test rapide.