1. Une fonction qui renvoie une fonction

def multiplicateur(n):
    def f(x):
        return x * n
    return f

double = multiplicateur(2)
triple = multiplicateur(3)
print(double(5))   # 10
print(triple(5))   # 15
Vocabulaire — closure
La fonction f renvoyée garde accès à la variable n de la fonction englobante, même après la fin de son exécution : c'est une closure (fermeture).

2. Exercice 1 — L'amplificateur de pouvoirs

a) Écris cree_amplificateur(facteur) qui renvoie une fonction amplifie(valeur). b) Crée deux amplificateurs différents. c) Sont-ils la même fonction ?

Voir la réponse
def cree_amplificateur(facteur):
    def amplifie(valeur):
        return valeur * facteur
    return amplifie

amplificateur_x3 = cree_amplificateur(3)
amplificateur_x10 = cree_amplificateur(10)
Ce sont deux fonctions différentes, chacune avec sa propre closure capturant sa valeur de facteur (vérifiable avec id()).

3. Composition de fonctions

def composer(f, g):
    def h(x):
        return f(g(x))
    return h

composer(f, g)(x) = f(g(x)) : g s'applique d'abord, puis f.

4. Exercice 2 — Le mixeur de potions

def majuscule(t): return t.upper()
def ajoute_exclamation(t): return t + " !"
def inverse(t): return t[::-1]

a) potion1 = majuscule puis exclamation. b) potion2 dans l'ordre inverse : même résultat ? c) Compose les trois.

Voir la réponse
potion1 = composer(ajoute_exclamation, majuscule)
print(potion1("magie"))   # "MAGIE !"
La composition n'est en général pas commutative : composer(f,g) ≠ composer(g,f) dès que les transformations interagissent (ex. avec inverse).

5. Exercice 3 — Chaîne de composition

Généralise à un nombre quelconque de fonctions avec reduce(composer, fonctions).

Voir la réponse
from functools import reduce
def composer_toutes(*fonctions):
    return reduce(composer, fonctions)
Avec reduce(composer, fonctions), la dernière fonction de la liste s'applique en premier sur la valeur.

Exercices défis (bonus) — applications

Défi 1 — Compte à rebours zen défi

Fonction sans argument qui affiche puis décrémente un compteur mémorisé entre les appels (indice : nonlocal).

Voir la réponse
def cree_compte_a_rebours(depart):
    n = depart
    def suivant():
        nonlocal n
        print(n)
        n -= 1
    return suivant
Défi 2 — Chronométreur magique défi
Voir la réponse
import time
def chronometre(fonction):
    def version(*args, **kwargs):
        debut = time.perf_counter()
        resultat = fonction(*args, **kwargs)
        print(f"{fonction.__name__} en {time.perf_counter()-debut:.6f}s")
        return resultat
    return version
Défi 3 — Adaptateur de signature défi
Voir la réponse
def adapte(fonction, valeur_fixee):
    def nouvelle(x):
        return fonction(valeur_fixee, x)
    return nouvelle
C'est le principe de functools.partial.