Apprenez en lisant dans l'ordre

Fonctions d'ordre supérieur — traiter les fonctions comme arguments et valeurs de retour

Apprends à traiter les fonctions Python comme des valeurs pour les passer en callbacks et les retourner d'autres fonctions.

Dans l'article précédent sur yield, tu as vu des fonctions qui produisent des valeurs une à une. Cette fois, tu restes côté fonctions et tu regardes les fonctions d'ordre supérieur — le mécanisme qui te permet de traiter une fonction elle-même comme une valeur.

Trois motifs à garder en tête : l'affecter à une variable, la passer en argument (un callback), et la retourner comme valeur.

Qu'est-ce qu'une fonction d'ordre supérieur — les fonctions sont aussi des valeurs

En Python, les fonctions sont des valeurs au même titre que les entiers ou les chaînes. Tu peux les mettre dans des variables, les passer à d'autres fonctions en argument, et les retourner comme résultats.

Une fonction qui prend une fonction en argument ou qui retourne une fonction comme résultat s'appelle une fonction d'ordre supérieur. Contrairement à print() ou len() qui se contentent de manipuler des valeurs, les fonctions d'ordre supérieur assemblent des morceaux de comportement.

Les trois motifs des fonctions d'ordre supérieur
①Affecter unefonction à une varf = printf("HELLO")②Passer unefonction en argumentafter(2, greet)→ callback③Retourner unefonction comme valeurmake_greeter("Alice")→ retourne une fonction
Comme les fonctions sont des valeurs, tu peux t'en servir de trois manières : affectation à une variable, passage en argument, et valeur de retour.

Affecter une fonction à une variable — les fonctions sont aussi des valeurs référencées

Quand tu définis une fonction avec def, le corps de la fonction est construit en mémoire et le nom de la fonction pointe vers cet emplacement. Affecte avec a = print, et a pointe maintenant vers le même corps de fonction, donc a("HELLO") et print("HELLO") font exactement la même chose.

L'astuce, c'est de ne pas mettre les () après le nom de la fonction. Ajoute () et tu appelles la fonction à la place, et c'est la valeur de retour qui est affectée.

Le nom d'une fonction n'est qu'un nom qui pointe vers un objet fonction
print(nom intégré)objet fonction(corps)SortieHELLOa(ton alias)réfèremême réfprint("HELLO")a("HELLO")
a = print              # pas de () — affecte le corps de fonction lui-même à a
a("HELLO")            # HELLO  ← pareil que print("HELLO")

print(id(print))       # par ex. 4395020128
print(id(a))           # la même adresse apparaît

# Tes propres fonctions marchent pareil
def greet():
    print("Bonjour")

say = greet            # construit un alias say
say()                  # Bonjour

Avec ou sans () change le sens

a = print est la forme qui passe la fonction, alors que a = print("HELLO") est la forme qui appelle la fonction et passe le résultat. Dans le second cas, la valeur de retour de print("HELLO") (None) atterrit dans a, donc appeler a() lève TypeError: 'NoneType' object is not callable.

Prends en main les fonctions comme valeurs, à la fois avec ta propre fonction et une fonction intégrée.

① Définis def greet(): avec un corps print("Bonjour").

② Construis un alias avec say = greet et appelle say() — vérifie que tu obtiens le même résultat que greet().

③ Donne aussi un alias à la fonction intégrée len avec f = len, et affiche le résultat de f("Python") avec print(...).

(Lorsque la réponse est correcte, l'explication apparaîtra.)

Éditeur Python

Exécuter le code pour voir le résultat

Passer une fonction en argument — les callbacks

L'usage le plus typique des fonctions d'ordre supérieur est le callback — « une fonction passée à une autre fonction et appelée à un moment précis », où « c'est l'appelant qui décide du comportement réel ».

Par exemple, imagine une routine qui « affiche une salutation pour une liste de trois noms », mais tu veux n'échanger que le style de salutation depuis le site d'appel. Garde le corps comme une boucle qui prend les noms un par un, et prends la partie formatage en argument fonction — alors l'appelant peut réutiliser la routine en changeant simplement le modèle de salutation.

def greet_all(names, formatter):
    for name in names:
        print(formatter(name))

def formal(name):
    return f"Bonjour {name}, merci pour votre fidélité."

def casual(name):
    return f"Salut, {name} !"

greet_all(["Alice", "Léa", "Hugo"], formal)
# Bonjour Alice, merci pour votre fidélité.
# Bonjour Léa, merci pour votre fidélité.
# Bonjour Hugo, merci pour votre fidélité.

greet_all(["Alice", "Léa", "Hugo"], casual)
# Salut, Alice !
# Salut, Léa !
# Salut, Hugo !
Un callback signifie « passer le comportement interne »
Côté appelantgreet_all(names, formal)Fonction d'ordre supérieurgreet_all(names, formatter)Pour chaque nom,appelle formatter(name)Callbackformal(name)"Bonjour Alice, ..."3 lignes de sortiepasse la fncorpsinterchangeablerésultat
Le corps de greet_all n'est qu'une boucle ; comment transformer chaque nom en ligne de salutation est laissé à l'argument formatter.

Construis une fonction d'ordre supérieur qui permet à l'appelant d'échanger le format de notification pour une liste de noms d'utilisateur.

① Définis def notify_all(users, formatter):. Avec for user in users:, appelle print(formatter(user)) pour chaque utilisateur.

② Construis def login_alert(name): qui retourne f"[Connexion] {name} s'est connecté".

③ Construis def logout_alert(name): qui retourne f"[Déconnexion] {name} s'est déconnecté".

④ Mets en place users = ["Alice", "Léa"], puis appelle notify_all(users, login_alert) et notify_all(users, logout_alert).

Éditeur Python

Exécuter le code pour voir le résultat

Brancher avec des callbacks dédiés à chaque cas

Tu n'es pas limité à passer un seul callback. « Utilise cette fonction en cas de succès, celle-là en cas d'échec » est aussi un usage naturel — partout où tu voudrais choisir entre plusieurs callbacks.

Par exemple, quand tu veux changer de comportement selon qu'un nombre est pair ou impair, la fonction qui décide peut ne faire qu'un branchement if, et laisser le traitement réel à deux fonctions fournies par l'appelant.

def process_number(number, even_callback, odd_callback):
    if number % 2 == 0:
        even_callback(number)
    else:
        odd_callback(number)

def handle_even(n):
    print(f"{n} est pair")

def handle_odd(n):
    print(f"{n} est impair")

process_number(4, handle_even, handle_odd)   # 4 est pair
process_number(7, handle_even, handle_odd)   # 7 est impair
Choisir le callback selon une condition
process_number(7,handle_even,handle_odd)n % 2 == 0 ?even_callback(n)→ handle_evenodd_callback(n)→ handle_odddécideTrueFalse

Construis une fonction d'ordre supérieur qui aiguille le traitement d'un paiement vers un callback en cas de succès et un autre en cas d'échec.

① Définis def process_payment(amount, on_success, on_failure):. Si amount > 0, appelle on_success(amount) ; sinon, appelle on_failure(amount).

② Construis def notify_success(amount): qui affiche f"Paiement de {amount} euros effectué".

③ Construis def notify_failure(amount): qui affiche f"Le montant {amount} est invalide".

④ Appelle process_payment(1500, notify_success, notify_failure) et process_payment(0, notify_success, notify_failure).

Éditeur Python

Exécuter le code pour voir le résultat

Retourner une fonction comme valeur — les clôtures en pratique

L'autre motif d'ordre supérieur, c'est « retourner une fonction comme valeur ». Retourner une fonction interne avec return a été couvert dans l'article sur les clôtures ; ici, l'angle pratique, c'est « remettre à l'appelant une fonction qui se souvient déjà de son réglage ».

Par exemple, quand tu veux produire en série des fonctions de log qui apposent le même préfixe à chaque appel, monte make_logger("INFO") pour le logger INFO et make_logger("ERROR") pour le logger ERROR — et le code appelant reste aussi court qu'info("Processus démarré").

def make_logger(prefix):
    def log(message):
        print(f"[{prefix}] {message}")
    return log               # retourne la fonction elle-même

info = make_logger("INFO")
error = make_logger("ERROR")

info("Processus démarré")     # [INFO] Processus démarré
error("Connexion échouée")  # [ERROR] Connexion échouée
info("Processus terminé")    # [INFO] Processus terminé
make_logger retourne une « fonction préconfigurée »
Module (espace de noms global)
  • info = make_logger("INFO") — reçoit une fonction qui se souvient de INFO
  • error = make_logger("ERROR") — reçoit une fonction distincte qui se souvient de ERROR
Frame de make_logger("INFO")
  • Contient prefix = "INFO"
  • return le log défini à l'intérieur
log (se souvient toujours de INFO)
  • Affiche [INFO] ... à chaque appel
Frame de make_logger("ERROR")
  • Contient prefix = "ERROR"
  • return une fonction log distincte
log (se souvient toujours de ERROR)
  • Un objet fonction distinct, indépendant d'info
Chaque appel à make_logger construit une fonction log distincte qui se souvient de son préfixe et la retourne. Les appelants l'utilisent sous des noms courts comme info() / error().

Construis une fonction d'ordre supérieur qui retourne une fonction de balisage qui se souvient de son tag.

① Définis def make_tagger(tag):. À l'intérieur, définis def tag_text(text): qui retourne f"<{tag}>{text}</{tag}>".

② À la fin, retourne la fonction interne avec return tag_text.

③ Construis deux fonctions : b = make_tagger("b") et i = make_tagger("i").

④ Appelle print(b("Important")) et print(i("Accentuation")) et vérifie que <b>Important</b> / <i>Accentuation</i> apparaissent respectivement.

Éditeur Python

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Qu'affiche ce code ?
def greet():
print("Salut")
f = greet
f()

Question 2Quelle ligne passe say_hi comme callback ?

Question 3Qu'affiche info("OK") après que ce code s'est exécuté ?
def make_logger(prefix):
def log(message):
print(f"[{prefix}] {message}")
return log
info = make_logger("INFO")