Du code, du communisme

Comment ne PAS utiliser une fonction anonyme (ou lambda) en Python

Dans le dernier article sur les lambdas, j’avais précisé que les lambdas étaient une question de style et qu’on pouvait faire sans. Voici différentes stratégies pour s’en passer.

Utiliser une inner function

En python on peut définir et exécuter à la volée une fonction dans une autre fonction/méthode ou une classe.

class Banane(object):
    def blougou(): # pas de self 🙂
        print "Sens giratoir inversé"
    blougou() # on peut appeler une fonction en dehors d'une méthode
    
    def methode(self): # une vraie méthode
        print "Vive Edgar Morin"
        # on peut définir une fonction dans une méthode
        def moukrene():
            print "A la glaviouse"
        moukrene()

Et ça donne:

Sens giratoir inversé # affichage à l'import
>>> Banane().methode()
Vive Edgar Morin
A la glaviouse

Préremplissez des arguments

Le module functools fournit tout un tas de goodies pour faire mumuse avec les fonctions, et notamment la fonction partial() qui prend une fonction en argument, et des arguments à passer à la fonction qu’on lui a passé en argument. Vous suivez ?
On s’en sert quand on veut utiliser une fonction, mais qu’on sait d’avance quels arguments on va lui passer:

>>> from functools import partial
>>> sum

>>> sum([2, 2])
4
>>> mini_sum = partial(sum, [2, 2])
>>> mini_sum()
4

On pas besoin de donner tous les arguments de la fonction à enrober: on peut passer les autres après.
Comme partial() retourne une fonction prête à être appelée, on peut l’utiliser là où on utiliserait une lambda:

>>> from __future__ import print_function
>>> def profit(etape1="On vole des caleçons", etape2=lambda: print('...')):
...         print(etape1)
...         etape2()
...
>>> profit()
On vole des caleçons
...

Se transforme en:

>>> def profit(etape1="On vole des caleçons", etape2=partial(print, '...')):
...         print(etape1)
...         etape2()
...
>>> profit()
On vole des caleçons
...

Utiliser les fonctions derrières les opérations de base

Quand vous utilisez un + ou un [], il s’agit ni plus ni moins d’une syntaxe raccourcie pour l’appel d’une fonction. Et Python vous laisse accéder à ces fonctions à travers le module operator.

>>> for operation in (lambda x: x[0], lambda x: x * 3):
    print(operation(ls))
...
a
['a', 3, 'a', 3, 'a', 3]
>>> from operator import itemgetter, mul
>>> for operation in (itemgetter(0), partial(mul, 3)):
    print(operation(ls))
...
a
['a', 3, 'a', 3, 'a', 3]

Le module operator est très riche: opérations logiques (incluant la négation), manipulation de slices, maths de base, set/get d’attributs/de clés/d’index… Il y a de quoi faire.

Mettez à la poubelle map et filter

Map permet d’appliquer une fonction a un itérable. Filter permet de choisir les éléments d’un itérable selon un critère pour former un autre itérable.

>>> nombres = range(10)
>>> paires = filter(lambda x: not x % 2, nombres)
>>> paires
[0, 2, 4, 6, 8]
>>> paires_au_carres = map(lambda x: x * x, paires)
>>> paires_au_carres
[0, 4, 16, 36, 64]

Les listes en intention remplacent avantageusement ces deux fonctions (je crois d’ailleurs qu’elles sont retirées des built-in en Python 3):

>>> [x * x for x in range(10) if not x % 2]
[0, 4, 16, 36, 64]

Je ne suis pas un allergique aux lambdas, et je les utilise assez souvent, mais il est bon de savoir qu’il existe des alternatives.