3.3 · Fonctions avancées : décorateurs, générateurs, lambdas, map/filter

Niveau 3 · Python intermédiaire

3.3Fonctions avancées : décorateurs, générateurs, lambdas, map/filter

Objectif : écrire du Python idiomatique et performant (lambdas, map/filter, générateurs yield, décorateurs @) — les outils du code professionnel et des frameworks.
Temps estimé : 16 min · lecture autonome · Si vous n'avez que 5 min : lisez « L'essentiel en 30s » + le schéma.

L'essentiel en 30 secondes

  • Lambda = fonction anonyme jetable d'une ligne : key=lambda t: t[1] pour trier — def dès que ça se nomme ou se réutilise.
  • Le décorateur enveloppe sans toucher : @chrono ajoute mesure, log ou contrôle en recevant la fonction et renvoyant une enveloppe — avec functools.wraps.
  • Le générateur produit à la demande : yield traite un fichier immense en mémoire constante — listes pour le petit, générateurs pour le grand.

Mini-cas filé — Nabil passe au pythonique

Nabil, data analyst junior à Alger, trie ses ventes avec des fonctions de trois lignes à usage unique et charge un export de plusieurs Go en liste — son poste rame. Il trie en une ligne (key=lambda v: v["montant"]), compare map/filter aux compréhensions, lit l'export ligne à ligne via un générateur yield, et mesure ses traitements avec un décorateur @chrono à functools.wraps. Même données, code court et poste soulagé.

Ce même cas revient dans le cours, le schéma, le script et le défi 48h.

1. Lambda, map/filter : le petit outillage

La lambda est une fonction anonyme d'une ligne, faite pour l'usage jetable en argument : sorted(ventes, key=lambda v: v["montant"]) trie sur critère sans nommer une fonction. map applique une fonction à chaque élément, filter ne garde que ceux qui satisfont une condition — mais la compréhension ([f(x) for x in xs if ok(x)]) est souvent plus lisible en Python.

Règle de Nabil : lambda pour du jetable court, def dès que ça se nomme, se réutilise ou dépasse une ligne. Une lambda de cinq lignes est un def déguisé : le nommer, le documenter, le tester. Cet outillage se croise partout (clés de tri, callbacks) — le connaître, c'est lire couramment le code des autres.

2. Décorateurs et générateurs : envelopper et alléger

Le décorateur enrichit une fonction sans modifier son code : il la reçoit, renvoie une enveloppe qui ajoute mesure, log, cache ou contrôle, via la syntaxe @decorateur. Toujours avec functools.wraps pour préserver nom et docstring. Nabil écrit @chrono qui affiche le temps d'exécution — et reconnaît le mécanisme derrière @app.route ou @pytest.fixture.

Le générateur (fonction à yield) produit ses valeurs une à une, à la demande : suspendu à chaque yield, repris à l'appel suivant. Un export de plusieurs Go se lit ligne par ligne en mémoire constante au lieu d'exploser la RAM. Listes pour le petit réutilisé, générateurs pour le grand ou l'infini : c'est le choix qui scale.

Erreur d'interprétation fréquente : « Lambda partout pour faire court, décorateur = magie, liste = toujours ». Non. La lambda longue devient illisible — nommez un def ; le décorateur sans wraps perd nom et doc et casse l'introspection ; la liste géante charge tout en mémoire. Jetable en lambda, transverse en décorateur avec wraps, grand en générateur : l'outil juste, pas le plus court.
Fonctions avancées : l'outil juste Jetable en lambda, transverse en décorateur, grand en générateur lambda (jetable)Tri en une ligne, def sinon+Décorateur (transverse)Enveloppe + wraps, sans toucher+yield (paresseux)Un élément à la fois, mémoire constante Listes pour le petit, générateurs pour le grand.
Jetable en lambda, transverse en décorateur, grand en générateur
Version texte du schéma (SEO / lecteurs d'écran / traduction) : 1) Jetable : Nabil trie ses ventes par montant avec key=lambda en une ligne, sans fonction nommée. 2) Transverse : son @chrono enveloppe chaque traitement pour mesurer, sans toucher leur corps, avec wraps. 3) Grand : l'export de plusieurs Go se lit via yield, une ligne à la fois. Leçon : chaque forme a son terrain — court, transverse, volumineux.

La liste qui a mangé la RAM

« Mon export de 8 Go en liste : le poste a chauffé tout l'après-midi à Alger. »
Générateur appris le soir même, ventilateur au repos

Grand flux, petit yield : la mémoire vous remercie.

🔎 Exemple concret — Chronométrer sans toucher + lire sans charger

Nabil mesure son traitement et lit un gros export ligne à ligne.

import time
import functools

def chrono(f):
    @functools.wraps(f)
    def enveloppe(*args, **kwargs):
        debut = time.perf_counter()
        resultat = f(*args, **kwargs)
        duree = time.perf_counter() - debut
        print(f"{f.__name__}: {duree:.2f}s")
        return resultat
    return enveloppe

def lignes(fichier):
    with open(fichier, encoding="utf-8") as f:
        for ligne in f:
            yield ligne.strip()

Lecture : @chrono mesure sans toucher le corps ; yield ne charge qu'une ligne à la fois.

Leçon : wraps préserve nom et docstring de la fonction décorée.

Vocabulaire de la section

Lambda
Fonction anonyme d'une ligne (lambda x: x * 2), utile pour un traitement court passé en argument.

Exemple simple : Le key=lambda v: v["montant"] du tri de Nabil.

Ce que ce n'est pas : une fonction nommée réutilisée et documentée.

Réflexe : lambda jetable, def dès que ça se nomme.

map / filter
map applique une fonction à chaque élément ; filter ne garde que ceux qui satisfont une condition.

Exemple simple : map(lambda x: x ** 2, montants) puis filter des pairs chez Nabil.

Ce que ce n'est pas : une compréhension souvent plus lisible.

Réflexe : comparer à la compréhension avant de choisir.

Générateur
Fonction à yield produisant ses valeurs une à une, à la demande : mémoire minimale, idéal gros flux.

Exemple simple : Les lignes d'export lues une par une par Nabil.

Ce que ce n'est pas : une liste géante chargée d'un coup en RAM.

Réflexe : yield dès que c'est grand ou infini.

yield
Renvoie une valeur en suspendant la fonction, reprise à l'appel suivant : le cœur des générateurs.

Exemple simple : Chaque yield ligne.strip() de l'export de Nabil.

Ce que ce n'est pas : un return qui termine la fonction.

Réflexe : itérer sans tout charger.

Décorateur
Fonction enveloppant une autre pour ajouter un comportement (chrono, log, contrôle), via @decorateur et functools.wraps.

Exemple simple : Le @chrono de Nabil sur ses traitements.

Ce que ce n'est pas : copier le code de mesure dans chaque fonction.

Réflexe : factoriser le transverse en décorateur.

En pratique — Écrire du Python pythonique

  1. Triez des tuples par 2e élément avec sorted(..., key=lambda t: t[1]) sur les ventes de Nabil.
  2. Transformez avec map(lambda x: x ** 2, nombres) et filtrez avec filter(lambda x: x % 2 == 0, nombres), puis comparez aux compréhensions.
  3. Écrivez un générateur à yield produisant les pairs à l'infini ; consommez-en 10 avec break.
  4. Créez un décorateur @chrono avec functools.wraps qui affiche le temps d'exécution, puis appliquez-le.
Vous maniez lambdas, map/filter, générateurs et décorateurs : code idiomatique, concis et efficace.

Script prêt à dire — présenter son code pythonique (30 s)

« Tri en lambda jetable sur le montant, générateur yield pour l'export volumineux en mémoire constante, décorateur chrono avec wraps pour mesurer sans toucher. Map et filter comparés aux compréhensions au cas par cas. »

À dire en revue ; montrer le @chrono et le yield.

Modèle copier-coller — décorateur chrono + générateur

import time
import functools

def chrono(f):
    @functools.wraps(f)
    def enveloppe(*args, **kwargs):
        debut = time.perf_counter()
        resultat = f(*args, **kwargs)
        print(f"{f.__name__}: {time.perf_counter() - debut:.2f}s")
        return resultat
    return enveloppe

@chrono
def total(montants):
    return sum(montants)

ventes_triees = sorted([{"v": "Alger", "m": 9}, {"v": "Oran", "m": 4}], key=lambda v: v["m"])

Indépendant

Un @chrono + un tri en lambda suffit à suivre les perfs des rapports.

PME à Alger

Nabil lit l'export caisses de plusieurs Go en générateur sans changer de poste.

Équipe produit

Imposer wraps dans tout décorateur pour garder noms et docs exploitables.

Débutant

Cette semaine : un tri en lambda et un générateur de pairs à l'infini.

✓ Bonnes pratiques

  • Réserver lambda au jetable court ; nommer un def dès que ça se réutilise.
  • Ajouter functools.wraps dans tout décorateur (nom et doc préservés).
  • Passer en yield dès que les données sont grandes ou infinies.

✗ Pièges fréquents

  • Écrire une lambda de cinq lignes illisible au lieu d'un def.
  • Oublier wraps (nom perdu, introspection cassée).
  • Charger un fichier géant en liste au lieu d'un générateur.

Points clés à retenir

  • LAMBDA = fonction anonyme d'UNE ligne, en argument (key de sorted…) ; sinon def.
  • DÉCORATEUR = enveloppe SANS toucher (@ + wraps) : mesure, log, contrôle factorisés.
  • GÉNÉRATEUR (yield) = production À LA DEMANDE, mémoire constante : le choix du grand.
  • Ces outils sont PARTOUT dans les frameworks : les manier, c'est lire le code des pros.

Défi 48h — appliquez demain

Écrivez 1 décorateur @chrono avec wraps et 1 générateur yield sur vos propres données.

Checklist sortie de section
  • Je trie en lambda sans fonction jetable nommée.
  • Mon décorateur préserve nom et doc via wraps.
  • Mon gros flux passe en générateur, pas en liste.
Auto-évaluation 1-5 : chaque lambda tient-elle en une ligne lisible ?un gros traitement passe-t-il encore par une liste ?

Questions fréquentes

Quand préférer un générateur à une liste ?

Quand les données sont volumineuses ou infinies : une liste charge tout en mémoire, un générateur produit une valeur à la fois. Pour lire un fichier de plusieurs Go ligne par ligne, le générateur est indispensable ; pour quelques éléments réutilisés, une liste suffit.

Un décorateur, à quoi ça sert concrètement ?

À ajouter un comportement répétitif (chrono, log, contrôle, cache) à plusieurs fonctions sans le recopier partout. Vous en utilisez déjà : @app.route, @staticmethod, @pytest.fixture — même mécanisme, même élégance.

Vérifiez votre compréhension

Pourquoi functools.wraps dans un décorateur ?

Ressources supplémentaires (optionnel, pour aller plus loin)

Examen du niveau 3Questions inédites et durcies pour valider vos acquis