Terminale NSI — Programmation fonctionnelle
Séance 3 — Closures et composition
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.