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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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() » :
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 :
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 :
Grâce à notre fonction de constructeur, nous créons une liste de personnes :
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 :
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 :
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 :
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 :
Ainsi que d’une autre paire de parenthèses entourant le ou les arguments :
Nous conseillons aux débutants de consulter notre tutoriel Python.