Lambdas Python : des fonctions anonymes en Python

Les fonctions lambda existent depuis la version 1.0 de Python comme un moyen de programmation fonctionnelle. Leur utilisation est désormais largement remplacée par d’autres techniques. Mais il existe toutefois encore quelques applications spécialisées que les programmeurs chevronnés de Python doivent connaître.

Que sont les fonctions lambda en Python ?

En langage Python, une « fonction lambda » est une fonction anonyme. Pour créer cette fonction lambda, il convient d’utiliser le mot-clé « lambda ». Cette expression se compose ainsi du mot-clé « lambda », suivi d’une liste d’arguments, de deux-points et d’une expression distincte (« expression »). À l’appel de la fonction lambda, l’expression est complétée par les arguments et évaluée :

lambda argument: expression

Les fonctions sont un élément de langage fondamental de presque tous les langages de programmation et représentent la plus petite unité de code réutilisable. Habituellement, les fonctions Python sont définies avec le mot-clé « def ». Voici par exemple la fonction carré, qui multiplie un nombre par lui-même :

# Définir la fonction carré
def square(num):
  return num * num
# Démontrer que cela fonctionne
assert square(9) == 81
python

Outre la méthode connue de définition des fonctions en Python à l’aide du mot-clé « def », ce langage utilise également les « lambdas ». Il s’agit de fonctions courtes et anonymes (sans nom) qui définissent une expression à l’aide de paramètres. Les lambdas peuvent être utilisées partout où une fonction est attendue ou peuvent être associées à un nom par assignation. Voici l’équivalent en version lambda de la fonction carré susmentionnée :

# Créer la fonction carré
squared = lambda num: num * num
# Démontrer que cela fonctionne
assert squared(9) == 81
python
Note

En langage Python, le terme « fonction lambda » désigne une fonction générée à l’aide du mot-clé lambda. Lambda n’est pas le nom d’une fonction et il ne s’agit pas non plus de l’un des opérateurs Python.

Quelle est la différence entre lambda et def ?

De prime abord, il semble étrange que Python, avec « lambda » et « def », dispose de deux moyens de créer des fonctions. Pourtant, « lambda » n’est pas une fonction à proprement parler, mais seulement une autre orthographe pour créer localement de courtes fonctions. Chaque fonction ayant été créée avec « lambda » est en effet également générée via « def ». L’inverse n’est toutefois pas vrai.

Au niveau syntaxique, « lambda » et « def » sont tous deux des mots-clés. La différence entre les deux réside toutefois dans la séparation stricte entre une instruction (« statement ») et une expression (« expression ») en langage Python. Pour résumer, les instructions sont des étapes dans l’exécution du code tandis que les expressions sont évaluées selon une valeur.

Le mot-clé « def » débute une instruction (spécifiquement une « compound statement ») qui contient d’autres instructions. Au sein de l’instruction « def », des instructions « return » peuvent apparaître. Lors de l’appel d’une fonction définie avec « def », une instruction « return » retourne une valeur.

Contrairement à l’instruction « def », le mot-clé « lambda » débute une expression qui ne doit pas contenir d’instructions. L’expression lambda accepte un ou plusieurs arguments et retourne une fonction anonyme. Si la fonction lambda créée est appelée, l’expression contenue est évaluée avec les arguments transmis, puis est retournée.

Quelles sont les restrictions des expressions lambda de Python ?

Python restreint de manière ciblée les utilisations des fonctions lambda, car en règle générale, il est préférable de nommer les fonctions. Cela oblige les programmeurs à réfléchir au sens de la fonction et à clairement délimiter les différentes parties.

Contrairement au corps d’une fonction définie via le mot-clé « def », les lambdas ne peuvent contenir aucune instruction. Il est donc impossible d’utiliser « if », « for », etc. dans une fonction lambda. Même le déclenchement d’une exception est impossible, car l’instruction « raise » est requise pour cela.

Les fonctions lambda en Python ne doivent contenir qu’une expression distincte qui sera évaluée lors de l’appel. Dans l’expression lambda, aucune annotation de type ne peut être utilisée. Désormais, d’autres techniques sont employées dans la majorité des cas d’utilisation des fonctions lambda en langage Python. On peut ici en particulier citer les listes en compréhension.

Pourquoi les fonctions lambda sont-elles utilisées en Python ?

En règle générale, les lambdas relèvent de la programmation fonctionnelle. Dans certains langages, comme JavaScript, les fonctions anonymes sont souvent utilisées sans qu’un mot-clé spécifique ne soit employé. En Python, les expressions lambda servent à générer localement de petites fonctions sans tout ce qui les accompagne. Nous vous présentons les cas d’utilisation les plus utiles.

Insérer des lambdas dans les fonctions d’ordre supérieur en Python

Les lambdas sont souvent utilisées en combinaison avec des fonctions d’ordre supérieur telles que « map() », « filter() » et « reduce() ». Grâce à elles, les éléments d’un objet « itérable » peuvent être transformés sans utiliser de boucle. Les fonctions d’ordre supérieur (ou « higher order functions » en anglais) sont définies comme des fonctions acceptant d’autres fonctions en tant que paramètre ou qui renvoie une fonction.

La fonction « map() » accepte une fonction et un itérable comme paramètres et exécute la fonction pour chaque élément de l’objet itérable. Examinons le problème que constitue la génération de nombres carrés : nous utilisons la fonction « map() » et indiquons une expression lambda comme argument, qui génère la fonction carré. Avec « map() », la fonction carré est appliquée à tous les éléments de la liste :

nums = [3, 5, 7]
# Nombres carrés à l’aide de « map() » et « lambda »
squares = map(lambda num: num ** 2, nums)
# Démontrer que cela fonctionne
assert list(squares) == [9, 25, 49]
python
Note

Depuis la version Python 3.0, les fonctions « map() » et « filter() » renvoient un itérable au lieu d’une liste. Dans l’instruction « assert », nous utilisons un appel « list() » afin d’extraire l’itérable dans une liste.

Grâce aux listes en compréhension, il existe désormais une approche plus moderne et privilégiée pour traiter les itérables. Au lieu d’utiliser « map() » et de générer une fonction lambda, nous décrivons directement l’opération :

nums = [3, 5, 7]
# Nombres carrés à l’aide de la liste en compréhension
squares = [num ** 2 for num in nums]
# Démontrer que cela fonctionne
assert squares == [9, 25, 49]
python

Grâce à la fonction « filter() », vous pouvez filtrer les éléments d’un itérable. Nous développons notre exemple de façon que seuls des nombres carrés précis soient générés :

# Liste de chiffres de 1 à 4
nums = [1, 2, 3, 4]
# Carré de chaque chiffre
squares = list(map(lambda num: num ** 2, nums))
# Filtrer les carrés pairs
even_squares = filter(lambda square: square % 2 == 0, squares)
# Démontrer que cela fonctionne
assert list(even_squares) == [4, 16]
python

Nous présentons à nouveau l’approche moderne privilégiée, à l’aide d’une liste en compréhension, pour générer le même résultat sans recourir aux lambdas ni aux fonctions d’ordre supérieur. Nous utilisons donc la partie « if » de la compréhension pour filtrer les éléments pairs des chiffres carrés obtenus :

# Liste de chiffres de 1 à 4 au carré
squares = [num ** 2 for num in range(1, 5)]
# Filtrer les carrés pairs
even_squares = [square for square in squares if square % 2 == 0]
# Démontrer que cela fonctionne
assert even_squares == [4, 16]
python
Note

La fonction « reduce() » de Python ne fait plus partie de la bibliothèque standard depuis la version Python 3.0. Elle a été déplacée dans le module « functools ».

Réaliser des fonctions de clé en Python, comme des lambdas

Les compréhensions ont majoritairement remplacé l’usage des fonctions d’ordre supérieur classiques « map() » et « filter() ». Grâce aux « fonctions de clé » (« key functions » en anglais), il existe toutefois un scénario d’application dans lequel les points forts des lambdas sont pleinement mis à profit.

Les fonctions de comparaison Python « sorted() », « min() » et « max() » fonctionnent sur la base d’itérables. Lors de l’appel, chaque élément de l’itérable est soumis à une comparaison. Chacune des trois fonctions accepte une fonction de clé comme paramètre « key » optionnel. La fonction de clé est appelée pour chaque élément et retourne une valeur de clé pour la comparaison.

Examinons le problème suivant à titre d’exemple. Nous avons un dossier contenant des fichiers image dont les noms sont reproduits dans une liste Python. Nous souhaitons trier cette liste. Les noms des fichiers commencent tous par « img » et sont suivis d’une numérotation :

# Liste des noms des fichiers image
images = ['img1', 'img2', 'img30', 'img3', 'img22', 'img100']
python

Si nous utilisons la fonction « sorted() » de Python, « l’ordre lexicographique » est utilisé. Cela signifie que les chiffres séquentiels sont traités en tant que chiffres uniques. Ainsi, les numéros « [‘1’, ‘2’, ‘100’] » sont présentés dans l’ordre « [‘1’, ‘100’, ‘2’] ». Le résultat ne correspond pas à nos attentes :

# Trier selon l’ordre lexicographique
sorted_image = sorted(images)
# Démontrer que cela fonctionne
assert sorted_image == ['img1', 'img100', 'img2', 'img22', 'img3', 'img30']
python

Afin de configurer le tri selon notre souhait, nous transmettons une expression « lambda » qui génère une fonction de clé. La fonction de clé extrait la partie numérique du nom d’un fichier, qui sera ensuite utilisée comme clé par la fonction « sorted() » :

# Extraire le composant numérique et trier en tant qu’entiers
sorted_image = sorted(images, key=lambda name: int(name[3:]))
# Démontrer que cela fonctionne
assert sorted_image == ['img1', 'img2', 'img3', 'img22', 'img30', 'img100']
python

La fonction de clé est utilisée localement uniquement et une seule fois. Il n’est pas nécessaire de définir à part une fonction nommée. Les lambdas sont ainsi le moyen approprié pour créer des fonctions de clé. Examinons deux autres exemples.

Outre « sorted() », les fonctions intégrées de Python « min() » et « max() » acceptent une fonction de clé optionnelle. Les fonctions trouvent le plus petit ou le plus grand élément d’une liste ou d’un autre itérable. Ce qui constitue précisément le plus petit ou le plus grand élément est une question de définition et est déterminé via la fonction de clé.

Dans une liste de valeurs simples, comme une liste de chiffres, ce que l’on entend par élément « le plus petit » ou « le plus grand » est évident. Aucune fonction de clé spéciale n’est ici requise :

nums = [42, 69, 51, 13]
assert min(nums) == 13
assert max(nums) == 69
python
Note

Si aucune fonction de clé n’est transmise, la fonction d’identité « f(x) = x » est utilisée de manière implicite. Cette fonction est aussi simple à définir que la lambda Python avec « lambda x:x ».

Qu’en est-il alors lorsque les éléments d’un itérable contiennent respectivement plusieurs données ? Imaginons une liste de dictionnaires qui représentent des personnes, avec leur nom et leur âge. D’après quels critères « min() » et « max() » doivent-ils déterminer quels éléments sont les plus petits ou les plus grands ? C’est là que la fonction de clé entre en jeu.

Pour illustrer le fonctionnement des fonctions de clé, nous avons besoin de données d’exemple. Nous créons une fonction « Personne() » servant de constructeur :

# Fonction de constructeur pour le dictionnaire représentant une personne
def Person(name, age):
  return {'name': name, 'age': age}
# Vérifier que cela fonctionne tel que prévu
assert Person('Jim', 42) == {'name': 'Jim', 'age': 42}
python

Grâce à notre fonction de constructeur, nous créons une liste de personnes :

# Créer un liste de personnes
people = [Person('Jim', 42), Person('Jack', 51), Person('John', 69)]
python

Finalement, nous trouvons la personne la plus âgée via l’appel « max() ». Nous créons ainsi, via une expression lambda, une fonction de clé, qui accepte un dictionnaire de personnes et en extrait l’âge comme un élément de comparaison :

# Trouver la personne la plus âgée
oldest = max(people, key=lambda person: person['age'])
# Vérifier que cela fonctionne
assert oldest == Person('John', 69)
python

L’approche fonctionne de la même manière pour la fonction « min() ». Nous définissons ici la fonction de clé en dehors de l’appel « min() », mais nous utilisons à nouveau l’expression lambda. Cela améliore la lisibilité et est avantageux lorsque la fonction de clé est utilisée localement de nombreuses fois :

# Définir la fonction de clé pour comparer les personnes selon leur âge
by_age = lambda person: person['age']
# Trouver la personne la plus jeune
youngest = min(people, key=by_age)
# Vérifier que cela fonctionne
assert youngest == Person('Jim', 42)
python

Créer des fermetures avec les lambdas Python

Les lambdas Python sont également utilisées dans la définition des fermetures (ou « closures » en anglais). Il s’agit là de fonctions créées à partir d’autres fonctions et qui enregistrent à cet effet une valeur. Les fermetures permettent par exemple de créer des familles de fonctions similaires. Voici un exemple courant expliquant la création de fonctions de puissance.

Les fonctions de puissance acceptent un argument et l’élèvent à la puissance correspondante. Les exemples connus sont la fonction carré « f(x) = x ^ 2 » et la fonction cube « f(x) = x ^ 3 ». Une fonction de constructeur permet de créer des fonctions de puissance quelconques en tant que fermetures. Nous utilisons une expression lambda et évitons ainsi de définir une fonction nommée interne :

# Définir la fonction de constructeur pour les fonctions de puissance
def power(n):
  return lambda num: num ** n

# Créer les fonctions carré et cube comme fermetures
square = power(2)
cubic = power(3)

# Démontrer que cela fonctionne
assert square(10) == 100
assert cubic(10) == 1000
python

Expression de fonction immédiatement invoquée (IIFE) avec les lambdas Python

Les IIFE (pour Immediately Invoked Function Expression ; prononcez « ayfi ») sont un modèle connu dans JavaScript. Une fonction anonyme est ainsi définie et immédiatement exécutée.

Lorsque cela s’avère peu utile même avec la limitation dans Python, les lambdas peuvent être utilisées comme IIFE. Nous avons seulement besoin de parenthèses autour de l’expression lambda :

(lambda num: num * num)
python

Ainsi que d’une autre paire de parenthèses entourant le ou les arguments :

assert (lambda num: num * num)(3) == 9
python
Conseil

Nous conseillons aux débutants de consulter notre tutoriel Python.