Tutoriel python gratuit / niveau intermédiaire –– LE lambda
Bienvenue dans le tutoriel python niveau intermédiaire numéro 4.
Fonctions à la volée
Tous les langages de programmation offrent la possibilité de déclarer des fonctions, c’est-à-dire des blocs de code séparés du programme principal, qui sont appelés par celui-ci en cas de besoin (pour plus d’informations voir ici dans mon tutoriel de base). On sait qu’en Python la déclaration d’une fonction se fait avec le mot clé def. Par exemple, dans un programme on pourrait écrire :
def carré (x): retour x ** 2 un = 5 b = carré (5) print ("Le carré de", a "est", b)
Dans l’exemple, le def sur la ligne # 1 est la définition de la fonction square, tandis que le programme principal commence sur la ligne # 4 et appelle la fonction sur la ligne suivante.
Cependant, Python propose une syntaxe alternative, qui permet de déclarer des fonctions simples dans le corps du programme principal, de manière plus compacte. Les fonctions ainsi déclarées sont appelées lambda (le nom dérive du calcul lambda, un système formel introduit dans la logique mathématique par Alonso Church).
Nous ouvrons IDLE et écrivons :
>>> lambda x : x ** 2 <function <lambda> at 0x035B8A50>
Nous notons qu’IDLE a reconnu le mot-clé lambda en le colorant en orange, et a répondu en disant qu’il a stocké une fonction dans un certain emplacement mémoire. Nous avons donc maintenant une fonction, mais que fait cette fonction et comment l’appelons-nous ? Continuez ainsi (je vous rappelle qu’en IDLE pour copier une ligne déjà écrite sur l’actuelle, il suffit de se déplacer dessus et d’appuyer sur ).
>>> (lambda x : x ** 2)(5) 25 >>> (lambda x : x ** 2)(12) 144
Avec un peu d’intuition, vous devriez commencer à comprendre : le premier x (à gauche des deux-points) est le paramètre formel de la fonction, tandis que l’expression x ** 2 à droite est la valeur de retour, qui est le carré de X. Donc
>>> lambda x : x ** 2
signifie “prendre un nombre x et retourner x ** 2”.
L’appel à la fonction nécessite à la place une syntaxe un peu plus compliquée : notons que, contrairement aux fonctions définies avec le def, les lambdas n’ont pas de nom avec lequel elles peuvent être appelées. Donc pour exécuter un lambda il faut écrire toute la définition entre parenthèses, puis écrire en dessous (toujours entre parenthèses, comme c’est requis par toutes les fonctions) les vrais paramètres à passer à la fonction. Fondamentalement, avec nos deux déclarations de l’exemple précédent, nous avons demandé à Python de calculer le carré de 5 et le carré de 12.
Au moyen des lambdas, Python laisse délibérément la possibilité de déclarer ce qu’on appelle en langage informatique des fonctions anonymes, c’est-à-dire des “fonctions sans nom” (nous y reviendrons plus tard). Cependant, comme appeler une fonction de cette manière est certainement compliqué, nous pouvons nommer les lambdas simplement en affectant leur définition à une variable, comme ceci :
>>> carré = lambda x : x ** 2 >>> carré (5) 25 >>> carré (12) 144
Ainsi l’appel à un lambda devient complètement le même que celui d’une fonction “normale” comme celle montrée dans l’exemple initial. La seule différence est que le lambda est déclaré “à la volée” sans avoir besoin d’une définition def détachée du programme principal.
Un lambda est une fonction anonyme définie par la syntaxe
arguments lambda : expression
c’est-à-dire le mot-clé lambda, la liste des arguments, les deux-points et une expression calculée à partir des arguments, qui sera la valeur renvoyée par la fonction.
On remarque que:
Un lambda n’est pas limité à un seul argument (comme dans l’exemple fait jusqu’ici) mais peut en avoir autant que l’on veut : il suffit de les écrire l’un après l’autre, séparés par des virgules ;
Le corps de la fonction doit être limité à une seule expression : il n’est donc pas possible d’utiliser des boucles, des instructions conditionnelles ou d’autres instructions en lambda (voir cependant le paragraphe suivant) ;
La fonction renvoie directement l’expression indiquée, et donc il n’y a pas besoin de retour non plus.
EXERCICE 4.1 : Tapez ces lignes de texte dans IDLE :
>>> fonction1 = lambda x, y : x + y >>> func2 = lambda x, y : x + " e " + y + " oui j'aime baiser" >>> fonction3 = lambda x : x <= 10
et essayez de comprendre ce que fait chaque fonction. Appelez-les plusieurs fois avec les paramètres appropriés, en observant le résultat renvoyé à chaque fois
EXERCICE 4.2 :
Vous définissez maintenant :
Un double lambda qui prend un paramètre et renvoie son double ;
Une somme lambda qui prend trois paramètres et renvoie leur somme ;
Un lambda pair qui prend un nombre en paramètre et renvoie True si le nombre est pair, False sinon (utilisez simplement une expression booléenne, comme dans le troisième exemple de l’exercice précédent).
Expérimentez en appelant les fonctions que vous avez définies avec divers paramètres.
Expressions lambda et conditionnelles
L’impossibilité d’utiliser des instructions if … else … dans les lambdas peut être contournée au moyen d’expressions conditionnelles, une construction syntaxique de Python (dans d’autres langages appelée opérateur ternaire) qui vous permet d’écrire des expressions simples basées sur un état. On écrit en IDLE :
>>> un = 5 >>> "petit nombre" si <100 sinon "grand nombre" 'Petit nombre' >>> un = 2000000 >>> "petit nombre" si <100 sinon "grand nombre" 'grand nombre'
Une expression comme
expression1 si condition sinon expression2
on l’appelle une expression conditionnelle : expression1 et expression2 sont deux expressions (qui peuvent renvoyer n’importe quelle valeur) tandis que condition est une expression booléenne : l’expression conditionnelle renvoie expression1 si la condition est vraie, expression2 si elle est fausse.
D’où l’expression :
"petit nombre" si <100 sinon "grand nombre"
ça veut dire:
- Il vérifie d’abord si l’expression booléenne à <100 est vraie ou fausse ;
- s’il est vrai, renvoie la première expression (c’est-à-dire la chaîne “petit nombre”
- s’il est faux, retournez la deuxième expression (c’est-à-dire la chaîne “grand nombre”
Autres exemples :
>>> 1 if 10 > 5 else 2 1 >>> x = 6 >>> y = 8 >>> x - y if x > y else x + y 14
Cette structure présente deux avantages :
Il est beaucoup plus compact qu’une construction if… else… traditionnelle.
C’est une expression Python, (et retourne donc une valeur, par opposition à if … else … qui est une série d’instructions). Cela permet par exemple d’affecter le résultat d’une expression conditionnelle à une variable :
>>> x = 5 si a <0 sinon 10 # en supposant que vous avez donné l'instruction a = 2000000 en premier >>>x dix
EXERCICE 4.3 : Écrivez une expression conditionnelle qui renvoie :
La chaîne “pair” si un nombre est pair, la chaîne “impair” sinon ;
Le carré d’un nombre b si b est inférieur à 100, sinon le nombre 10000 ;
(un peu plus difficile) Le carré d’un nombre c si ce carré est inférieur à 100, sinon le nombre 100
Dans IDLE, vous affectez des valeurs aux variables a, b, c et rappelez les expressions que vous avez écrites, en vous assurant qu’elles se comportent comme prévu.
>>> func = lambda x : "petit nombre" si x% lt ; 100 sinon "grand nombre" >>> fonction (10) 'Petit nombre' >>> fonction (1000000) 'grand nombre'
EXERCICE 4.4 : Définir :
Une fonction lambda4 qui accepte un nombre et renvoie 5 si le nombre est supérieur à 10, 6 sinon
Une fonction lambda qui accepte un nombre et renvoie la moitié du nombre s’il est pair, ou le nombre diminué de un s’il est impair ;
Un lambda moins0 qui accepte deux nombres x et y et renvoie leur différence s’il est positif ou 0, 0 sinon (cela peut être fait de plusieurs façons, en utilisant une expression conditionnelle ou une fonction Python intégrée).
Objets appelables
Avant de continuer, je pense qu’il est nécessaire de s’attarder un instant sur la syntaxe que nous utilisons lorsque nous appelons une fonction. Si nous essayons d’écrire le nom d’une fonction Python sans parenthèses ni arguments, nous voyons que nous n’obtenons aucune erreur :
<built-in function print>
En effet, le nom d’une fonction (sans parenthèses) est pour Python une variable comme une autre : il peut s’agir d’une fonction prédéfinie (c’est-à-dire une fonction prédéfinie, comme print dans l’exemple), d’une fonction définie avec def ou d’un lambda a auquel nous avons donné un nom. Lorsque nous appelons la fonction, Python voit les parenthèses de l’appel comme n’importe quel autre opérateur, c’est-à-dire un symbole (comme +, -, * etc.) qui lie deux ou plusieurs variables ou constantes ensemble (dans notre cas, la fonction et ses arguments) et renvoie une valeur (le résultat de la fonction). On peut donc dire que les fonctions sont génériquement des objets pour lesquels l’opérateur () est défini : ces objets sont appelés callable (c’est-à-dire callable objects). Ce discours est un peu théorique, mais pour le clarifier essayons de faire quelques expériences en IDLE :
>>> un = 2 >>> b = 3 >>> a + b # opérateur + entre deux int : OK 5 >>> a (b) # opérateur (): erreur Traceback (dernier appel le plus récent) : Fichier "<pyshell # 17>", ligne 1, dans un B) TypeError : l'objet 'int' n'est pas appelable
Dans les deux premières lignes nous avons assigné aux variables a et b deux entiers ; dans le troisième, nous avons utilisé l’opérateur d’addition qui a renvoyé leur somme. Ensuite, nous avons essayé d’utiliser des parenthèses : Python a répondu qu’un int (c’est-à-dire le nombre 2 contenu dans a) n’est pas appelable et nous a donné une erreur. Maintenant continuons comme ça :
>>> a = lambda x: x ** 2 # maintenant a est un appelable >>> a + b # opérateur + : erreur Traceback (dernier appel le plus récent) : Fichier "<pyshell # 19>", ligne 1, dans un + b TypeError : type(s) d'opérande non pris en charge pour + : 'fonction' et 'int' >>> a (b) # opérateur (): OK 9
Maintenant, à la place, nous attribuons à un appelable (un lambda) : l’opérateur + provoque une erreur (Python explique qu’il n’est pas possible d’ajouter un nombre et une fonction !) Tandis que les () renvoient le résultat de l’appel (c’est-à-dire le carré de b).
Ces considérations seront très utiles pour aborder le paragraphe suivant.
QUAND UTILISER les lambdas
Après avoir appris à utiliser les lambdas, une question pourrait facilement venir à l’esprit : à quoi servent-ils ? La réponse n’est pas très simple : pratiquement tout ce qui peut être fait avec un lambda pourrait aussi être fait avec une fonction normale, et donc, à l’exception de quelques cas simples dans lesquels nous pouvons raccourcir un peu notre code avec une fonction définie “sur le voler”, vous ne voyez pas de grandes applications.
En fait, les fonctions anonymes telles que les lambdas montrent leur utilité surtout dans des situations assez compliquées et sophistiquées, qui ne sont probablement pas à la portée d’un débutant. Leur utilisation classique est d’implémenter les fonctions dites d’ordre supérieur, c’est-à-dire des fonctions qui prennent en paramètres (ou retournent) d’autres fonctions. Pour comprendre de quoi il s’agit, donnons tout de suite un exemple, cette fois en utilisant l’éditeur :
import math
def fai_qualcosa(f, x):
return f(x)
print(fai_qualcosa(math.sqrt, 4))
On observe qu’à la ligne #4 on “appelle” le premier paramètre f en lui donnant le deuxième argument. C’est parfaitement légal, car comme je l’ai dit plus haut pour Python les parenthèses sont un opérateur comme un autre, qui peut s’appliquer à deux variables. Mais bien sûr notre do_something devra être appelé avec un callable comme premier argument : regardez l’appel dans # 6 : le premier argument est le nom de la fonction math.sqrt (note : sans parenthèses, car dans # 6 nous sommes traiter sqrt comme une variable quelconque à passer en paramètre à faire_quelquechose). Si nous exécutons le programme, il imprimera 2 (la racine carrée de 4).
Si maintenant nous voulions passer à la fonction do_something une fonction définie par nous, les lambdas deviendraient très utiles : modifiez le programme comme ceci :
def faire_quelque chose (f, x): retour f (x) print (faire_quelque chose (lambda x : 2 * x, 8)) print (faire_quelque chose (lambda x : x + 1, 8)
Fondamentalement, nous pouvons dire à la fonction do_something nous-mêmes “que faire” avec le deuxième argument en lui passant une fonction comme premier argument. Imaginons que nous ayons une vingtaine d’alternatives différentes : si nous n’avions pas les lambdas nous devrions déclarer (avec le def) chacune des fonctions, en donnant à chacune un nom différent, et mettre leur nom à la place du lambda dans l’appel to do_something (nous devons donc retenir le nom de toutes les fonctions que nous avons écrites, au risque de faire des erreurs). Avec les lambdas, nous pouvons écrire directement le corps de la fonction sans avoir à lui donner un nom. De tels cas sont assez fréquents dans la programmation d’interfaces graphiques, dans lesquelles l’utilisateur peut choisir entre de nombreuses actions différentes et le programme doit interpréter et exécuter son choix.
personnes = [ {"Nom": "Luigi", "Nom": "Bianchi", "Âge": 35}, {"Nom": "Pietro", "Nom": "Verdi", "Âge": 38}, {"Nom": "Laura", "Nom": "Rossi", "Âge": 32}, {"Nom": "Giuseppe", "Nom": "Neri", "Âge": 41} ]
Si nous essayons de le trier, nous obtenons une erreur, car Python ne permet pas de comparer deux dictionnaires.
>>> personnes.sort () Traceback (dernier appel le plus récent) : Fichier "<pyshell # 1> :", ligne 1, dans personnes.sort () TypeError : '<' non pris en charge entre les instances de 'dict' et 'dict'
Mais nous pouvons dire le type de comparaison des noms de famille des personnes à l’aide du paramètre clé optionnel (une fonction qui renvoie la valeur que nous devons comparer):
>>> Personnes.sort (Key = Lambda X: X ["Nom"]) >>> personnes [{'Nom': 'Luigi', 'Nom de famille': 'Bianchi', 'Âge: 35}, {'Nom': 'Giuseppe', 'Nom': 'Neri', 'Age': 41}, {'Nom': 'Laura', 'Nom': 'Rossi', 'Age': 32}, {'Nom': 'Pietro', 'Nom': 'Verdi', 'Age': 38}]
Dans l’appel au tri, nous avons indiqué dans le paramètre clé A Lambda: cela prend une variable X de la liste des personnes et renvoie son champ “Nom”. De cette manière, lorsque le tri compare deux personnes de la liste, seuls leurs noms de famille apparaîtront en les commandant par ordre alphabétique.
EXERCICE 4.5 Essayez de modifier le lambda dans le paramètre clé avec les autres champs du dictionnaire : observez comment l’ordre de la liste change en appelant la sorte
Voici une autre application du paramètre clé :
>>> noms = ["Andrea", "Ada", "Massimo", "Luigi", "Valentina"] >>> noms.sort (key = len) >>> noms ['Ada', 'Luigi', 'Andrea', 'Massimo', 'Valentina']
Cette fois, le tri pourrait normalement trier la liste par ordre alphabétique, mais nous avons demandé de la trier en utilisant (pour comparer deux chaînes) la fonction len qui nous donne leur longueur. Nous n’avions même pas besoin d’un lambda car nous avions déjà le len intégré prêt.
EXERCICE 4.6 Réécrivez l’exemple précédent en utilisant un lambda comme clé (regardez les exemples précédents, vous devez toujours utiliser la fonction len).
EXERCICE 4.7 Écrivez deux instructions qui renvoient la personne la plus jeune et la plus âgée de la liste des personnes (les fonctions max et min ont également le paramètre key facultatif).
Si ce dernier paragraphe vous a semblé plutôt obscur, ne vous inquiétez pas : comme je l’ai dit, les lambdas sont des objets pour les programmeurs experts ; tout au plus vous arrive-t-il de remplacer une simple fonction par un lambda auquel vous avez donné un nom. Au fur et à mesure que vous deviendrez plus expérimenté, vous vous rendrez compte de leur utilité.