Les fonctions d'ordre supérieur : des fonctions qui reçoivent et renvoient des fonctions
- Expliquer ce qu’est une fonction d’ordre supérieur (Higher-Order Function) et pourquoi c’est possible en Python
- Passer une fonction en paramètre — sans parenthèses — et l’appeler depuis une autre fonction
- Utiliser map, filter et le paramètre key de sorted, max et min
- Écrire une fonction qui renvoie une fonction
Qu’allons-nous apprendre ?
- Ce qu’est une fonction d’ordre supérieur (Higher-Order Function).
- Comment passer une fonction en paramètre — et pourquoi sans parenthèses.
- Les fonctions intégrées
mapetfilter. - Le paramètre
keydesorted,maxetmin. - Une fonction qui renvoie une fonction.
Pourquoi en avons-nous besoin ?
Deux fonctions presque identiques :
def double_all(numbers):
result = []
for n in numbers:
result.append(n * 2)
return result
def square_all(numbers):
result = []
for n in numbers:
result.append(n * n)
return resultLa boucle est identique — seule l’action sur chaque élément diffère. Chaque nouvelle action (moitié, plus 10, arrondi…) demande une fonction entière de plus, et c’est exactement la duplication que DRY interdit.
L’idée : écrire la boucle une seule fois, et passer l’action elle-même en paramètre.
Explication à partir des bases
Une fonction est un objet
Nous avons déjà vu dans la leçon « les fonctions en profondeur » qu’en Python une fonction est un objet — comme un nombre ou une liste :
double(sans parenthèses) — la fonction elle-même. On peut la stocker dans une variable ou la transmettre.double(5)(avec parenthèses) — un appel à la fonction. Le résultat est ce qu’elle renvoie.
La fonction d’ordre supérieur
Une fonction d’ordre supérieur est une fonction qui :
- Reçoit une autre fonction en paramètre, ou
- Renvoie une fonction.
Vous en avez déjà utilisé une : sorted(products, key=lambda p: p[1]) — sorted reçoit une fonction dans le paramètre key.
Une machine à laver sait « passer sur tous les vêtements ». Ce qu’il faut faire à chaque pièce — vous le choisissez avec le programme : lavage délicat, normal ou séchage. On n’achète pas une nouvelle machine pour chaque programme — on lui donne le programme. La fonction d’ordre supérieur est la machine, et la fonction que vous passez est le programme.
Syntaxe
def apply_to_all(numbers, action): # action is a function
result = []
for n in numbers:
result.append(action(n)) # call it for each item
return result
apply_to_all([1, 2, 3], double) # pass it: no parentheses
list(map(double, [1, 2, 3])) # built-in: map
list(filter(is_even, [1, 2, 3, 4])) # built-in: filter
sorted(words, key=len) # key: sort by the result of lenPremier exemple de code
week-2/apply_to_all.py :
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))Sortie :
[2, 4, 6]
[1, 4, 9]
[2, 2]Analyse du code ligne par ligne
| Ligne | Explication |
|---|---|
def apply_to_all(numbers, action): | action est un paramètre ordinaire — simplement, nous attendons qu’il contienne une fonction. |
result.append(action(n)) | Nous appelons la fonction reçue, sur chaque élément. |
apply_to_all([1, 2, 3], double) | Nous passons double sans parenthèses — la fonction, pas son résultat. |
lambda n: n * n | Une courte fonction sans nom, créée sur place. |
round | On peut aussi passer une fonction intégrée. |
Un autre exemple
map, filter et key. week-2/hof_builtins.py :
prices = [100, 250, 40, 80]
with_fee = list(map(lambda p: p + 10, prices))
cheap = list(filter(lambda p: p < 100, prices))
print(with_fee)
print(cheap)
words = ["banana", "fig", "cherry", "kiwi"]
print(sorted(words, key=len))
print(max(words, key=len))
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(max(students, key=lambda s: s[1]))Sortie :
[110, 260, 50, 90]
[40, 80]
['fig', 'kiwi', 'banana', 'cherry']
banana
('Dana', 92)map(func, items)— exécutefuncsur chaque élément.list(...)transforme le résultat en liste.filter(func, items)— ne garde que les éléments pour lesquelsfuncrenvoieTrue.key=len—sortedetmaxcomparent selon le résultat delensur chaque mot, et non selon le mot lui-même.
Une fonction qui renvoie une fonction. week-2/make_multiplier.py :
def make_multiplier(factor):
def multiply(n):
return n * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))
print(triple(5))Sortie :
10
15make_multiplier est une « usine » qui produit des fonctions : chaque appel renvoie une nouvelle fonction, qui se souvient de son propre factor. Comment elle s’en souvient exactement — nous le verrons dans la leçon suivante, sur les fonctions imbriquées.
Erreur courante
L’erreur : appeler la fonction au lieu de la passer.
apply_to_all([1, 2], double())TypeError: double() missing 1 required positional argument: 'n'Pourquoi ? double() avec des parenthèses appelle la fonction immédiatement — sans paramètre — elle échoue donc avant même que apply_to_all ne commence. Passez la fonction sans parenthèses : apply_to_all([1, 2], double). C’est la fonction qui la reçoit qui l’appellera.
Exercices
Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.
Exercice 1: Une fonction en paramètre
Écrivez une fonction apply_to_all(numbers, action) qui exécute action sur chaque nombre et renvoie une nouvelle liste. Utilisez-la avec une fonction ordinaire double, avec une lambda qui met au carré, et avec round.
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))Résultat attendu :
[2, 4, 6]
[1, 4, 9]
[2, 2]Afficher la solution
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))[2, 4, 6]
[1, 4, 9]
[2, 2]Passez la fonction sans parenthèses (double, round). C’est apply_to_all qui l’appelle.
Exercice 2: map et filter
prices = [20, 75, 120, 45, 90]- Ne gardez que les prix supérieurs à 50, avec
filter. - Ajoutez 10 à chaque prix restant, avec
map.
Résultat attendu :
[75, 120, 90]
[85, 130, 100]Afficher la solution
prices = [20, 75, 120, 45, 90]
expensive = list(filter(lambda p: p > 50, prices))
raised = list(map(lambda p: p + 10, expensive))
print(expensive)
print(raised)[75, 120, 90]
[85, 130, 100]filter ne garde que ce pour quoi la lambda renvoie True. map exécute la lambda sur chaque élément. list(...) transforme le résultat en liste.
Exercice 3: key — selon quoi trier
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]- Triez les mots par longueur.
- Trouvez le mot le plus long.
- Triez les étudiants par note, de la plus haute à la plus basse, et n’affichez que les noms.
Résultat attendu :
['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
AviAfficher la solution
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(sorted(words, key=len))
print(max(words, key=len))
for name, grade in sorted(students, key=lambda s: s[1], reverse=True):
print(name)['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
Avikey=len — comparer selon le résultat de len sur chaque mot, et non selon le mot lui-même.
Exercice 4: Une fonction qui renvoie une fonction
Écrivez une fonction make_discount(percent) qui renvoie une fonction, laquelle reçoit un prix et le renvoie après la remise.
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))Résultat attendu :
180.0
100.0Afficher la solution
def make_discount(percent):
def apply(price):
return price - price * percent / 100
return apply
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))180.0
100.0make_discount est une « usine » — chaque appel produit une nouvelle fonction qui se souvient de son propre percent.
Exercice 5: Appeler au lieu de passer
def double(n):
return n * 2
print(apply_to_all([1, 2], double()))- Quelle erreur obtiendrez-vous, et pourquoi ?
- Quelle est la correction ?
Afficher la solution
TypeError: double() missing 1 required positional argument: 'n'. Les parenthèses dedouble()appellent la fonction immédiatement — sans paramètre — avant même queapply_to_allne commence.- Passez la fonction elle-même, sans parenthèses :
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2], double))[2, 4]Résumé
- Fonction d’ordre supérieur = une fonction qui reçoit une fonction ou renvoie une fonction. C’est possible parce qu’une fonction est un objet.
- Passez une fonction sans parenthèses — c’est la fonction qui la reçoit qui l’appelle.
map— une action sur chaque élément.filter— garde ce qui remplit la condition.key=danssorted,max,min— définit selon quoi comparer.- Une fonction peut renvoyer une fonction — une « usine » qui produit des fonctions sur mesure.