Python a des capacités de programmation fonctionnelle honorables, mais loin de ce que peuvent offrir des langages spécialisés comme Javascript ou Lisp.
Notamment, en JS, on peut créer des fonctions anonymes (appelée aussi ‘lambdas’), c’est à dire un bloc de code réutilisable comme une fonction, appelable comme une fonction, mais sans nom:
function(){ alert('Le Chateau de Aaarrrgh') } |
A quoi ça sert ?
Concrètement, à rien. Il n’existe aucune opération qu’on fasse avec une fonction anonyme qu’on ne puisse faire avec une fonction normale. On utilise les fonctions anonymes par goût, pour des raisons des style.
En effet, les lambdas sont très pratiques pour créer des fonctions jetables: quand on a besoin d’une fonction, mais que l’on ne va l’utiliser qu’une seule fois. Car on peut définir et utiliser une fonction anonyme presque d’une traite, ce qui évite l’écriture en deux temps.
Par exemple en JS, avec jQuery vous allez faire ça:
// quand on clic sur une lien, faire un alert $('a').click(function(){ alert("C'est chiant hein ?") }) |
Ici le bloc:
function(){ alert("C'est chiant hein ?") } |
Est une fonction anonyme. On ne compte pas la réutiliser, donc inutile de la mettre à part: on la définit et on la passe en callback tout de suite.
Les lambdas en Python
Python possède ce genre de fonctionnalité, à l’aide du mot clé lambda
. Une fonction:
def gratter(sujet): return "Je me gratte %s" % sujet |
Peut aussi s’écrire:
gratter = lambda sujet: "Je me gratte %s" % sujet |
C’est exactement la même chose, seule la syntaxe change:
lambda
au lieu dedef
;- pas de paranthèses;
- pas de mot clé
return
.
Ce qui est pratique, c’est qu’on peut définir la fonction à la volée. Par exemple, supposons que vous souhaitiez créer un mapping de fonctions de décompression:
import bz2 import zlib from base64 import decodestring from collections import defaultdict def ne_fait_rien(x): return x def decompresse_bz(x): return bz2.decompress(decodestring(x)).decode('utf8') def decompresse_zip(x): return zlib.decompress(decodestring(x)).decode('utf8') def retourne_ne_fait_rien(): return ne_fait_rien decompresseur = defaultdict(retourne_ne_fait_rien) decompresseur['bz'] = decompresse_bz decompresseur['zip'] = decompresse_zip |
Et ça s’utilise comme ça:
resultat = decompresseur[format](data) |
Pratique si vous avez un script qui va décompresser un max de données venues de l’extérieur et qui annoncent leur format sous forme de string. Au pire des cas, si vous ne connaissez pas le format, ça ne fait rien.
Maintenant voyons la même chose avec des fonctions anonymes:
import bz2 import zlib from base64 import decodestring from collections import defaultdict decompresseur = defaultdict(lambda x: x) decompresseur['bz'] = lambda x: bz2.decompress(decodestring(x)).decode('utf8') decompresseur['zip'] = lambda x: zlib.decompress(decodestring(x)).decode('utf8') |
Comme les lambdas peuvent être définies n’importe où, le code est beaucoup plus court. On peut décrire la logique de notre code directement là où on en a besoin, et en l’occurence, on se fiche d’avoir la fonction sous son état ordinnaire.
Les limites des lambdas
Guido a bridé volontairement les lambdas en Python:
- On ne peut les écrire que sur une ligne.
- On ne peut pas avoir plus d’une instruction dans la fonction.
Difficile, donc, de se la jouer full nested pendant tout le script comme on ferait en Haskell.
Une autre limite vient du fait que le système de portée de Python est lexical. Ainsi:
ajouteurs= range(4) for i in range(4): ajouteurs[i] = lambda a: i + a |
Qui devrait produire:
>>> print ajouteurs[3](3) 6 >>> print ajouteurs[2](3) 5 |
Produit en fait:
>>> print ajouteurs[3](3) 6 >>> print ajouteurs[2](3) 6 |
Pour contourner cela, il faut forcer Python a recrééer un scope à chaque création de lambda:
>>> for i in range(4): ... ajouteurs [i] = lambda a, i=i: i + a ... >>> print( ajouteurs[2](3)) 5 |
Cela fonctionne en passant i
en tant que valeur par defaut d’un paramètre du même nom.
dire que javascript est un exemple pour la programmation fonctionnelle ne serait-il pas un peu trompeur ?
Un exemple, un exemple… C’est clair que ça manque de map, filter et tout le toutim, mais underscore.js permet de remplacer tout ça.
J’ai pris javascript, non pas parcequ’il arrive à tenir tête à Lisp ou Haskell sur ce terrain, mais parceque c’est le langage fonctionnel que le plus de gens connaissent. Et je me vois mal donner le même exemple en C.
Bijour, l’exemple de la fin avec ‘ajouteurs’ ne fonctionne pas :(
>IndexError: list assignment index out of range<
Je voulais tester etant donne que je n'ai pas tres bien saisi la notion de portee lexicale dans ce contexte !
J’ai oublié d’initialiser la liste d’ajouteurs. Ca marche maintenant :-)
J’ai trouvé ça dans les stats:
python fonctions anonymes sans lambda
A tout ceux qui cherchent ça, il n’y a PAS de moyen de faire des fonctions anonymes en Python sans lambda.
Les fonctions anonymes en Javascript peuvent en au moins un cas être nécessaires : les bookmarklets.
En effet, pour ne pas risquer de rentrer en conflit avec le JS de la page (nom de fonction déjà utilisée), utiliser une fonction anonyme est la seule solution 100% fiable.
“Il n’existe aucune opération qu’on fasse avec une fonction anonyme qu’on ne puisse faire avec une fonction normale”
Pas tout à fait d’accord…
Une fonction lambda est irremplacable dans certaines opérations, comme par exemple l’argument cmp des fonctions sorted() et list.sort() .
L’argument cmp est deprecated et n’existe plus en Python 3.
Mais même pour l’argument cmp en Python 2, on peut utiliser sans problème une fonction normale. Par ailleurs, le module operator fournie des fonctions toutes faites pour la plupart des use cases.