32  Les fonctions lambda

Les fonctions lambda sont des fonctions anonymes que l’on définit en une ligne, là où on en a besoin. Elles sont omniprésentes dès qu’on utilise sorted, filter, map, ou des fonctions qui attendent un autre fonction en paramètre. Le TOSA Avancé les teste systématiquement.

32.1 Qu’est-ce qu’une fonction lambda ?

Une lambda est une fonction sans nom, définie en une expression unique.

Fonction classique

def carre(x):
    return x ** 2

print(carre(5))
25

Équivalent lambda

carre = lambda x: x ** 2

print(carre(5))
25

Anatomie

lambda  x, y   :   x + y
  │     │         │
  │     │         expression (le retour)
  │     paramètres
  mot-clé

Points à noter :

  • Le mot-clé lambda remplace def.
  • Pas de return : l’expression après : est implicitement renvoyée.
  • Pas de parenthèses autour des paramètres.
  • Une seule expression : pas de statements (pas de if/else en bloc, pas de boucle, pas de print).

32.2 Utilisations typiques

Dans 99 % des cas, on utilise une lambda pour passer un petit comportement à une fonction qui en attend un.

Avec sorted() — le cas #1

Trier selon un critère :

mots = ["banane", "kiwi", "pomme", "fraise"]

# Par longueur
print(sorted(mots, key=lambda m: len(m)))

# Par dernière lettre
print(sorted(mots, key=lambda m: m[-1]))
['kiwi', 'pomme', 'banane', 'fraise']
['banane', 'pomme', 'fraise', 'kiwi']

Tri de listes de tuples

etudiants = [("Alice", 14), ("Bob", 8), ("Charlie", 17), ("Diana", 9)]

# Par note
print(sorted(etudiants, key=lambda t: t[1]))

# Par note décroissante
print(sorted(etudiants, key=lambda t: t[1], reverse=True))
[('Bob', 8), ('Diana', 9), ('Alice', 14), ('Charlie', 17)]
[('Charlie', 17), ('Alice', 14), ('Diana', 9), ('Bob', 8)]

Tri multi-critères avec tuple

etudiants = [
    ("Alice", "A", 14),
    ("Bob",   "B", 17),
    ("Charlie", "A", 17),
    ("Diana", "B", 14),
]

# Par note décroissante, puis par nom
classement = sorted(etudiants, key=lambda t: (-t[2], t[0]))
for x in classement:
    print(x)
('Bob', 'B', 17)
('Charlie', 'A', 17)
('Alice', 'A', 14)
('Diana', 'B', 14)

La clé lambda t: (-t[2], t[0]) renvoie un tuple : tri primaire par -note (équivalent à décroissant), secondaire par nom.

Avec filter()

nombres = [1, -2, 3, -4, 5, -6, 7]

# Ne garder que les positifs
positifs = list(filter(lambda n: n > 0, nombres))
print(positifs)
[1, 3, 5, 7]
Compréhensions : souvent préférables à filter/map

Une compréhension fait la même chose, plus lisiblement :

nombres = [1, -2, 3, -4, 5, -6, 7]

# ✅ Préférez
positifs = [n for n in nombres if n > 0]
print(positifs)
[1, 3, 5, 7]

filter et map restent utiles dans un style fonctionnel ou quand on a déjà la fonction sous la main (fonction nommée existante).

Avec map()

nombres = [1, 2, 3, 4]

# Carrés
carres = list(map(lambda n: n ** 2, nombres))
print(carres)
[1, 4, 9, 16]

Équivalent : [n ** 2 for n in nombres].

Avec max() / min()

etudiants = [
    {"nom": "Alice", "note": 14},
    {"nom": "Bob",   "note": 17},
    {"nom": "Eve",   "note": 12},
]

meilleur = max(etudiants, key=lambda e: e["note"])
print("Meilleur :", meilleur)

faible = min(etudiants, key=lambda e: e["note"])
print("Plus faible :", faible)
Meilleur : {'nom': 'Bob', 'note': 17}
Plus faible : {'nom': 'Eve', 'note': 12}

Avec reduce() (pour culture)

Le module functools propose reduce qui agrège une liste selon une fonction binaire :

from functools import reduce

# Somme (équivalent à sum)
total = reduce(lambda a, b: a + b, [1, 2, 3, 4, 5])
print(total)

# Produit
produit = reduce(lambda a, b: a * b, [1, 2, 3, 4, 5])
print(produit)

# Maximum (équivalent à max)
plus_grand = reduce(lambda a, b: a if a > b else b, [3, 1, 4, 1, 5, 9, 2])
print(plus_grand)
15
120
9

En pratique, sum, min, max sont préférés à reduce quand ils existent.

32.3 Les lambdas peuvent avoir plusieurs paramètres

# Deux paramètres
additionner = lambda a, b: a + b
print(additionner(3, 5))

# Trois paramètres
volume = lambda L, l, h: L * l * h
print(volume(2, 3, 4))

# Zéro paramètre
constante = lambda: 42
print(constante())
8
24
42

32.4 Limitations des lambdas

Les lambdas sont volontairement limitées pour rester lisibles :

1. Une seule expression

Pas de return, pas de boucle, pas de if/elif en bloc.

# ❌ Impossible
f = lambda x:
    if x > 0:
        return x
    else:
        return -x

# ✅ Possible avec une ternaire
f = lambda x: x if x > 0 else -x

2. Pas d’annotations de type

# ❌ Impossible
f = lambda x: int -> int: x * 2

# ✅ Utilisez une fonction normale si vous voulez annoter
def doubler(x: int) -> int:
    return x * 2

3. Pas de docstring

Une lambda ne peut pas être documentée. Si votre fonction mérite une docstring, n’utilisez pas lambda.

32.5 Lambda vs fonction normale : quand choisir ?

Règle pratique
  • Lambda : pour un petit comportement jetable, passé comme argument. Idéalement en une ligne.
  • def : pour toute fonction réutilisée, complexe, ou méritant un nom.

Quand vous hésitez, préférez def — c’est plus lisible.

Exemples de bonnes utilisations

# ✅ Clé de tri simple, jetable
sorted(notes, key=lambda n: n["score"])

# ✅ Filtre ponctuel
list(filter(lambda x: x > 0, valeurs))

# ✅ Fonction callback courte
bouton.on_click = lambda: print("clic !")

Mauvaises utilisations

# ❌ Affecter une lambda à un nom — utilisez def
carre = lambda x: x ** 2

# → Préférez :
def carre(x):
    return x ** 2
⚠️ PEP 8 déconseille l’affectation de lambda

La PEP 8 dit explicitement : « Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier. »

Raison : def carre(x): return x ** 2 est aussi court mais plus lisible et débogable (la fonction a un vrai nom, visible dans les traces d’erreur).

Les lambdas sont faites pour être passées en argument, pas stockées sous un nom.

32.6 Le piège classique : capture de variable dans une boucle

Un cas surprenant qui tombe parfois au TOSA :

# Créer une liste de fonctions qui multiplient par 1, 2, 3 ?
multiplicateurs = [lambda x: x * i for i in range(1, 4)]

# Problème !
for m in multiplicateurs:
    print(m(10))   # on s'attend à 10, 20, 30
30
30
30

Pourquoi ? Les 3 lambdas capturent la même variable i, qui vaut 3 à la fin de la boucle.

Solution : forcer la capture par une valeur par défaut.

multiplicateurs = [lambda x, i=i: x * i for i in range(1, 4)]

for m in multiplicateurs:
    print(m(10))   # 10, 20, 30 comme attendu
10
20
30

i=i évalue i au moment de la création de la lambda et le fige.

32.7 operator : alternative aux lambdas

Pour des lambdas ultra-simples comme lambda t: t[1] ou lambda o: o.note, le module operator offre une alternative plus rapide et plus lisible :

from operator import itemgetter, attrgetter

etudiants = [("Alice", 14), ("Bob", 17), ("Eve", 12)]

# Avec lambda
print(sorted(etudiants, key=lambda t: t[1]))

# Avec itemgetter (mieux)
print(sorted(etudiants, key=itemgetter(1)))
[('Eve', 12), ('Alice', 14), ('Bob', 17)]
[('Eve', 12), ('Alice', 14), ('Bob', 17)]

itemgetter(1) renvoie une fonction équivalente à lambda t: t[1] mais plus rapide (implémentée en C).

attrgetter("attribut") est l’équivalent pour accéder à un attribut d’objet (voir POO).


🧩 Quiz 3.1 — Les fonctions lambda

Question 1

Que fait lambda x: x * 2 ?

  1. Définit une fonction qui renvoie x * 2
  2. Calcule directement x * 2
  3. Renvoie la chaîne "x * 2"
  4. Une erreur

a) — crée une fonction anonyme qui attend x et renvoie x * 2. Il faut l’appeler pour obtenir le résultat.

f = lambda x: x * 2
print(f)        # objet fonction
print(f(5))     # appel → 10
<function <lambda> at 0x0000015FA3014FE0>
10

Question 2

Quelle syntaxe est invalide ?

  1. lambda x: x + 1
  2. lambda x, y: x + y
  3. lambda: 42
  4. lambda x: return x + 1

d)pas de return dans une lambda. L’expression après : est implicitement renvoyée. La bonne forme est lambda x: x + 1.

f = lambda x: return x + 1
  Cell In[16], line 1
    f = lambda x: return x + 1
                  ^
SyntaxError: invalid syntax

Question 3

Que renvoie sorted([3, 1, 2], key=lambda x: -x) ?

  1. [1, 2, 3]
  2. [3, 2, 1]
  3. [-3, -2, -1]
  4. Une erreur

b) [3, 2, 1] — trier x par -x croissant revient à trier x décroissant. Équivalent à reverse=True.

print(sorted([3, 1, 2], key=lambda x: -x))
[3, 2, 1]

Question 4

Quelle expression est équivalente à list(map(lambda x: x * 2, [1, 2, 3])) ?

  1. [x * 2 in [1, 2, 3]]
  2. [x * 2 for x in [1, 2, 3]]
  3. map([1, 2, 3], x * 2)
  4. [1, 2, 3] * 2

b) — la compréhension est plus lisible et équivalente : [x * 2 for x in [1, 2, 3]].

print(list(map(lambda x: x * 2, [1, 2, 3])))
print([x * 2 for x in [1, 2, 3]])
[2, 4, 6]
[2, 4, 6]

Question 5

Que renvoie sorted(["banane", "kiwi", "pomme"], key=lambda m: len(m)) ?

  1. ["banane", "kiwi", "pomme"]
  2. ["kiwi", "pomme", "banane"]
  3. ["kiwi", "banane", "pomme"]
  4. ["pomme", "banane", "kiwi"]

b) ["kiwi", "pomme", "banane"] — tri par longueur croissante : kiwi (4), pomme (5), banane (6).

print(sorted(["banane", "kiwi", "pomme"], key=lambda m: len(m)))
['kiwi', 'pomme', 'banane']

Question 6

Quelle affirmation est FAUSSE ?

  1. Une lambda ne peut avoir qu’une seule expression
  2. Une lambda peut avoir plusieurs paramètres
  3. Une lambda peut contenir un print() et un return
  4. Une lambda est une fonction anonyme

c) — une lambda n’a pas de return explicite (pas autorisé), et elle ne peut contenir qu’une seule expression. Pas de print suivi d’autre chose.

Question 7

Que recommande la PEP 8 ?

  1. Utiliser des lambdas partout pour la concision
  2. Affecter des lambdas à des variables nommées
  3. Préférer def à l’affectation nom = lambda...
  4. Ne jamais utiliser de lambdas

c) — si vous avez besoin d’une fonction nommée, utilisez def. Les lambdas sont faites pour être passées comme argument, pas stockées.

# ❌ Déconseillé par PEP 8
carre = lambda x: x ** 2

# ✅ Recommandé
def carre(x):
    return x ** 2

✏️ Exercice 3.1 — Tris multiples

Soit une liste de produits :

produits = [
    {"nom": "Pain",    "prix": 1.20, "stock": 50},
    {"nom": "Lait",    "prix": 0.95, "stock": 100},
    {"nom": "Fromage", "prix": 4.50, "stock": 20},
    {"nom": "Vin",     "prix": 12.0, "stock": 5},
    {"nom": "Miel",    "prix": 7.80, "stock": 15},
]

En utilisant des lambdas, produisez 4 tris différents :

  1. Par prix croissant.
  2. Par stock décroissant.
  3. Par nom (alphabétique).
  4. Par prix × stock décroissant (valeur totale en stock).
produits = [
    {"nom": "Pain",    "prix": 1.20, "stock": 50},
    {"nom": "Lait",    "prix": 0.95, "stock": 100},
    {"nom": "Fromage", "prix": 4.50, "stock": 20},
    {"nom": "Vin",     "prix": 12.0, "stock": 5},
    {"nom": "Miel",    "prix": 7.80, "stock": 15},
]

# 4 tris à produire...
produits = [
    {"nom": "Pain",    "prix": 1.20, "stock": 50},
    {"nom": "Lait",    "prix": 0.95, "stock": 100},
    {"nom": "Fromage", "prix": 4.50, "stock": 20},
    {"nom": "Vin",     "prix": 12.0, "stock": 5},
    {"nom": "Miel",    "prix": 7.80, "stock": 15},
]

print("Par prix croissant :")
for p in sorted(produits, key=lambda x: x["prix"]):
    print(f"  {p['nom']:<10} {p['prix']:>6.2f} €")

print("\nPar stock décroissant :")
for p in sorted(produits, key=lambda x: x["stock"], reverse=True):
    print(f"  {p['nom']:<10} {p['stock']}")

print("\nPar nom :")
for p in sorted(produits, key=lambda x: x["nom"]):
    print(f"  {p['nom']}")

print("\nPar valeur totale en stock (décroissante) :")
for p in sorted(produits, key=lambda x: x["prix"] * x["stock"], reverse=True):
    valeur = p["prix"] * p["stock"]
    print(f"  {p['nom']:<10} {valeur:>7.2f} €")
Par prix croissant :
  Lait         0.95 €
  Pain         1.20 €
  Fromage      4.50 €
  Miel         7.80 €
  Vin         12.00 €

Par stock décroissant :
  Lait       100
  Pain       50
  Fromage    20
  Miel       15
  Vin        5

Par nom :
  Fromage
  Lait
  Miel
  Pain
  Vin

Par valeur totale en stock (décroissante) :
  Miel        117.00 €
  Lait         95.00 €
  Fromage      90.00 €
  Pain         60.00 €
  Vin          60.00 €

✏️ Exercice 3.2 — Filtres avec lambda

À partir d’une liste de nombres, produisez en une ligne pour chaque cas :

  1. Les pairs.
  2. Les multiples de 3 entre 10 et 100.
  3. Les nombres premiers à un seul chiffre (2, 3, 5, 7).
nombres = list(range(1, 101))

# 1. Pairs
pairs = ...

# 2. Multiples de 3 entre 10 et 100
mult3 = ...

# 3. Premiers à 1 chiffre
premiers = list(filter(lambda n: n in (2, 3, 5, 7), nombres))
nombres = list(range(1, 101))

# 1. Pairs
pairs = list(filter(lambda n: n % 2 == 0, nombres))
print("Pairs (10 premiers) :", pairs[:10])

# 2. Multiples de 3 entre 10 et 100
mult3 = list(filter(lambda n: n % 3 == 0 and 10 <= n <= 100, nombres))
print("Multiples de 3 :", mult3)

# 3. Premiers à 1 chiffre
premiers = list(filter(lambda n: n in (2, 3, 5, 7), nombres))
print("Premiers :", premiers)
Pairs (10 premiers) : [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Multiples de 3 : [12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99]
Premiers : [2, 3, 5, 7]
nombres = list(range(1, 101))

pairs = [n for n in nombres if n % 2 == 0]
mult3 = [n for n in nombres if n % 3 == 0 and 10 <= n <= 100]
premiers = [n for n in nombres if n in (2, 3, 5, 7)]

print("Pairs (10 premiers) :", pairs[:10])
print("Multiples de 3 :", mult3[:10], "...")
print("Premiers :", premiers)
Pairs (10 premiers) : [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Multiples de 3 : [12, 15, 18, 21, 24, 27, 30, 33, 36, 39] ...
Premiers : [2, 3, 5, 7]

✏️ Exercice 3.3 — max avec critère dynamique

Écrivez une fonction meilleur(etudiants, critere) qui renvoie l’étudiant ayant le score le plus haut selon un critère donné (nom d’attribut).

Exemple :

etudiants = [
    {"nom": "Alice", "maths": 14, "francais": 18},
    {"nom": "Bob",   "maths": 17, "francais": 11},
    {"nom": "Eve",   "maths": 12, "francais": 15},
]

meilleur(etudiants, "maths")      # → Bob
meilleur(etudiants, "francais")   # → Alice
def meilleur(etudiants, critere):
    ...

etudiants = [
    {"nom": "Alice", "maths": 14, "francais": 18},
    {"nom": "Bob",   "maths": 17, "francais": 11},
    {"nom": "Eve",   "maths": 12, "francais": 15},
]

print(meilleur(etudiants, "maths"))
print(meilleur(etudiants, "francais"))
def meilleur(etudiants, critere):
    return max(etudiants, key=lambda e: e[critere])

etudiants = [
    {"nom": "Alice", "maths": 14, "francais": 18},
    {"nom": "Bob",   "maths": 17, "francais": 11},
    {"nom": "Eve",   "maths": 12, "francais": 15},
]

print(meilleur(etudiants, "maths"))
print(meilleur(etudiants, "francais"))
{'nom': 'Bob', 'maths': 17, 'francais': 11}
{'nom': 'Alice', 'maths': 14, 'francais': 18}
from operator import itemgetter

def meilleur(etudiants, critere):
    return max(etudiants, key=itemgetter(critere))

etudiants = [
    {"nom": "Alice", "maths": 14, "francais": 18},
    {"nom": "Bob",   "maths": 17, "francais": 11},
]

print(meilleur(etudiants, "maths"))
{'nom': 'Bob', 'maths': 17, 'francais': 11}

C’est équivalent et plus rapide sur de grandes listes.


✏️ Exercice 3.4 — Le piège de la capture

Que s’affiche-t-il ? Expliquez, puis corrigez le code pour obtenir 10 20 30.

fonctions = [lambda: i * 10 for i in range(1, 4)]
for f in fonctions:
    print(f(), end=" ")

Exécutez le code et expliquez le résultat. Corrigez-le ensuite.

Sortie incorrecte :

fonctions = [lambda: i * 10 for i in range(1, 4)]
for f in fonctions:
    print(f(), end=" ")
30 30 30 

Pourquoi ? Les 3 lambdas capturent la même variable i, qui vaut 3 à la fin de la boucle. Tous donnent donc 3 * 10 = 30.

Correction : forcer la capture avec un paramètre par défaut.

fonctions = [lambda i=i: i * 10 for i in range(1, 4)]
for f in fonctions:
    print(f(), end=" ")
10 20 30 

i=i fige la valeur courante de i au moment de la création de chaque lambda. C’est un piège fréquent quand on génère des lambdas dans une boucle.


À retenir

Points clés du chapitre
  1. Lambda : fonction anonyme, une expression, pas de return explicite.
  2. Usage #1 : sorted(..., key=lambda ...), filter, map, max(..., key=...), min(..., key=...).
  3. Tri multi-critères : key=lambda t: (-t[1], t[0]) — tuple avec signes.
  4. Limites : pas de statements, pas de if/elif en bloc, pas de docstring, pas d’annotations.
  5. PEP 8 : ne pas affecter une lambda à un nom — utilisez def.
  6. Compréhensions souvent préférables à map/filter avec lambda.
  7. operator.itemgetter(i) et attrgetter("x") : alternatives plus rapides.
  8. Piège de capture : lambda x=x: ... pour figer la valeur courante dans une boucle.

← Chapitre précédent : *args et **kwargsChapitre suivant : POO (1/2) →