Comment utiliser les fonctions Python lambda – Real Python

By | juin 19, 2019

Expert Python

Python et d'autres langages tels que Java, C # et même C ++ ont vu leur syntaxe être complétée par des fonctions lambda, alors que des langages comme LISP ou la famille de langages ML, Haskell, OCaml et F #, utilisent le concept fondamental de lambdas.

Les lambdas Python sont de petites fonctions anonymes, soumises à une syntaxe plus restrictive mais plus concise que les fonctions Python classiques.

À la fin de cet article, vous saurez:

  • Comment les lambdas de Python ont été créés
  • Comment les lambdas se comparent-ils aux objets de fonction habituels
  • Comment écrire des fonctions lambda
  • Quelles fonctions de la bibliothèque standard Python utilisent les lambdas
  • Quand utiliser ou éviter les fonctions Python lambda

Ce tutoriel s'adresse principalement aux programmeurs Python de niveau intermédiaire à expérimenté, mais il est accessible à tous les esprits curieux intéressés par la programmation et le calcul lambda.

Tous les exemples inclus dans ce tutoriel ont été testés avec Python 3.7.

Lambda Calculus

Les expressions lambda en Python et dans d'autres langages de programmation ont leurs racines dans le lambda calcul, un modèle de calcul inventé par Alonzo Church. Vous découvrirez quand le lambda calcul a été introduit et pourquoi il s’agit d’un concept fondamental qui s’est retrouvé dans l’écosystème Python.

L'histoire

Alonzo Church a officialisé le lambda calcul, un langage basé sur l'abstraction pure, dans les années 1930. Les fonctions lambda sont également appelées abstractions lambda, une référence directe au modèle d’abstraction de la création originale de Alonzo Church.

Le calcul lambda peut encoder n'importe quel calcul. C'est Turing complet, mais contrairement au concept d'une machine de Turing, il est pur et ne garde aucun état.

Les langages fonctionnels trouvent leur origine dans la logique mathématique et le calcul lambda, tandis que les langages de programmation impératifs englobent le modèle de calcul basé sur des états inventé par Alan Turing. Les deux modèles de calcul, lambda calcul et Turing, peuvent être traduits l'un en l'autre. Cette équivalence est connue sous le nom d'hypothèse Church-Turing.

Les langages fonctionnels héritent directement de la philosophie du lambda calcul, en adoptant une approche déclarative de la programmation qui met l'accent sur l'abstraction, la transformation des données, la composition et la pureté (pas d'état et pas d'effets secondaires). Des exemples de langages fonctionnels incluent Haskell, Lisp ou Erlang.

En revanche, la machine de Turing a conduit à une programmation impérative dans des langages comme Fortran, C ou Python.

Le style impératif consiste à programmer avec des instructions, en guidant pas à pas le déroulement du programme avec des instructions détaillées. Cette approche favorise la mutation et nécessite un état de gestion.

La séparation dans les deux familles présente certaines nuances, car certains langages fonctionnels intègrent des fonctionnalités impératives, telles que OCaml, tandis que des fonctionnalités fonctionnelles imprègnent la famille de langages impératifs, en particulier avec l'introduction des fonctions lambda en Java, ou Python.

Python n'est pas intrinsèquement un langage fonctionnel, mais il a adopté très tôt certains concepts fonctionnels. En janvier 1994, carte(), filtre(), réduire(), et le lambda opérateur ont été ajoutés à la langue.

Premier exemple

Voici quelques exemples pour vous donner envie de lire du code fonctionnel Python.

La fonction d'identité, une fonction qui retourne son argument, est exprimée avec une définition standard de la fonction Python à l'aide du mot clé def comme suit:

>>>

>>> def identité(X):
...   revenir X

identité() prend une dispute X et le renvoie sur invocation.

En revanche, si vous utilisez une construction Python lambda, vous obtenez ce qui suit:

Dans l'exemple ci-dessus, l'expression est composée de:

  • Le mot clé: lambda
  • Une variable liée: X
  • Un corps: X

Vous pouvez écrire un exemple un peu plus élaboré, une fonction qui ajoute 1 à un argument, comme suit:

Vous pouvez appliquer la fonction ci-dessus à un argument en entourant la fonction et son argument avec des parenthèses:

>>>

>>> (lambda X: X + 1) (2)
3

La réduction est une stratégie de calcul lambda pour calculer la valeur de l'expression. Il consiste à substituer l'argument 2 pour X:

(lambda x: x + 1) (2) = lambda 2: 2 + 1
                     = 2 + 1
                     = 3

Puisqu'une fonction lambda est une expression, elle peut être nommée. Par conséquent, vous pouvez écrire le code précédent comme suit:

>>>

>>> ajoute un = lambda X: X + 1
>>> ajoute un(2)
3

La fonction lambda ci-dessus équivaut à écrire ceci:

def ajoute un(X):
    revenir X + 1

Ces fonctions prennent toutes un seul argument. Vous avez peut-être remarqué que, dans la définition des lambdas, les arguments ne sont pas entourés de parenthèses. Les fonctions multi-arguments (fonctions qui prennent plus d'un argument) sont exprimées en lambdas Python en listant les arguments et en les séparant par une virgule (,) mais sans les entourer de parenthèses:

>>>

>>> nom complet = lambda premier, dernier: F'Nom complet: first.title () last.title ()'
>>> nom complet('guido', 'van rossum')
'Nom complet: Guido Van Rossum'

La fonction lambda assignée à nom complet prend deux arguments et retourne une chaîne interpolant les deux paramètres premier et dernier. Comme on pouvait s'y attendre, la définition du lambda répertorie les arguments sans parenthèses, alors que l'appel de la fonction se fait exactement comme une fonction Python normale, les parenthèses entourant les arguments.

Fonctions anonymes

Les termes suivants peuvent être utilisés indifféremment selon le type et la culture du langage de programmation:

  • Fonctions anonymes
  • Fonctions Lambda
  • Expressions lambda
  • Abstractions Lambda
  • Forme lambda
  • Littéraux de fonction

Pour le reste de cet article après cette section, vous verrez surtout le terme fonction lambda.

Pris à la lettre, une fonction anonyme est une fonction sans nom. En Python, une fonction anonyme est créée avec le lambda mot-clé. Plus vaguement, un nom peut lui être attribué. Considérons une fonction anonyme à deux arguments définie avec lambda mais pas lié à une variable. Le lambda n'a pas de nom:

>>>

>>> lambda X, y: X + y

La fonction ci-dessus définit une expression lambda qui prend deux arguments et retourne leur somme.

Mis à part le fait de vous informer que ce formulaire convient parfaitement à Python, il ne mène à aucune utilisation pratique. Vous pouvez appeler la fonction dans l'interpréteur Python:

L’exemple ci-dessus tire parti de la fonctionnalité interactive uniquement pour les interprètes fournie par le trait de soulignement (_). Voir la note ci-dessous pour plus de détails.

Vous ne pouvez pas écrire de code similaire dans un module Python. Prendre en compte _ dans l'interprète comme un effet secondaire dont vous avez profité. Dans un module Python, vous attribuez un nom au lambda ou vous transmettez le lambda à une fonction. Vous utiliserez ces deux approches plus tard dans cet article.

Un autre modèle utilisé dans d'autres langages tels que JavaScript consiste à exécuter immédiatement une fonction Python lambda. Ceci est connu comme un Expression de fonction immédiatement appelée (IIFE, prononcez «iffy»). Voici un exemple:

>>>

>>> (lambda X, y: X + y) (2, 3)
5

La fonction lambda ci-dessus est définie puis immédiatement appelée avec deux arguments (2 et 3). Il retourne la valeur 5, qui est la somme des arguments.

Plusieurs exemples de ce tutoriel utilisent ce format pour mettre en évidence l'aspect anonyme d'une fonction lambda et éviter de se focaliser sur lambda en Python comme un moyen plus court de définir une fonction.

Python n'encourage pas l'utilisation des expressions lambda invoquées immédiatement. Cela résulte simplement du fait qu'une expression lambda est appelable, contrairement au corps d'une fonction normale.

Les fonctions Lambda sont fréquemment utilisées avec des fonctions d'ordre supérieur, qui prennent une ou plusieurs fonctions en tant qu'arguments ou renvoient une ou plusieurs fonctions.

Une fonction lambda peut être une fonction d'ordre supérieur en prenant une fonction (normale ou lambda) comme argument, comme dans l'exemple suivant:

>>>

>>> high_ord_func = lambda X, func: X + func(X)
>>> high_ord_func(2, lambda X: X * X)
6
>>> high_ord_func(2, lambda X: X + 3)
7

Python expose les fonctions d'ordre supérieur en tant que fonctions intégrées ou dans la bibliothèque standard. Les exemples comprennent carte(), filtre(), functools.reduce (), ainsi que des fonctions clés comme Trier(), triés (), min (), et max (). Vous utiliserez les fonctions lambda avec les fonctions d’ordre supérieur Python dans Utilisation appropriée des expressions lambda.

Python Lambda et fonctions régulières

Cette citation de la FAQ sur la conception et l’historique de Python semble donner le ton quant aux attentes générales concernant l’utilisation des fonctions lambda en Python:

Contrairement aux formes lambda dans d’autres langues, où elles ajoutent des fonctionnalités, les lambdas en Python ne sont qu’une notation abrégée si vous êtes trop paresseux pour définir une fonction. (La source)

Néanmoins, ne laissez pas cette déclaration vous dissuader d’utiliser Python. lambda. À première vue, vous pouvez accepter qu’une fonction lambda soit une fonction avec un sucre syntaxique raccourcissant le code pour définir ou appeler une fonction. Les sections suivantes soulignent les points communs et les différences subtiles entre les fonctions Python normales et les fonctions lambda.

Les fonctions

À ce stade, vous pouvez vous demander ce qui distingue fondamentalement une fonction lambda liée à une variable d’une fonction régulière avec un seul revenir ligne: sous la surface, presque rien. Voyons comment Python voit une fonction construite avec une seule instruction return par rapport à une fonction construite comme une expression (lambda).

le dis module expose des fonctions pour analyser le bytecode Python généré par le compilateur Python:

>>>

>>> importation dis
>>> ajouter = lambda X, y: X + y
>>> type(ajouter)

>>> dis.dis(ajouter)
        1 0 LOAD_FAST 0 (x)
                                                        2 LOAD_FAST 1 (y)
                                                        4 BINARY_ADD
                                                        6 RETURN_VALUE
>>> ajouter
<fonction  à l'adresse 0x7f30c6ce9ea0>

Tu peux voir ça dis () expose une version lisible du bytecode Python permettant l'inspection des instructions de bas niveau que l'interpréteur Python utilisera lors de l'exécution du programme.

Maintenant, voyez-le avec un objet de fonction régulière:

>>>

>>> importation dis
>>> def ajouter(X, y): revenir X + y
>>> type(ajouter)

>>> dis.dis(ajouter)
        1 0 LOAD_FAST 0 (x)
                                                        2 LOAD_FAST 1 (y)
                                                        4 BINARY_ADD
                                                        6 RETURN_VALUE
>>> ajouter

Le bytecode interprété par Python est le même pour les deux fonctions. Mais vous remarquerez peut-être que le nom est différent: le nom de la fonction est ajouter pour une fonction définie avec def, alors que la fonction Python lambda est vue comme lambda.

Traceback

Vous avez vu dans la section précédente que, dans le contexte de la fonction lambda, Python ne fournissait pas le nom de la fonction, mais uniquement: . Il peut s’agir d’une limitation à prendre en compte lorsqu’une exception se produit et que le suivi ne montre que :

>>>

>>> div_zéro = lambda X: X / 0
>>> div_zéro(2)
Traceback (dernier appel le plus récent):
    Fichier "", ligne 1, dans <module>
    Fichier "", ligne 1, dans <lambda>
ZeroDivisionError: division par zéro

Le suivi d’une exception déclenchée lors de l’exécution d’une fonction lambda identifie uniquement la fonction à l’origine de l’exception. .

Voici la même exception déclenchée par une fonction normale:

>>>

>>> def div_zéro(X): revenir X / 0
>>> div_zéro(2)
Traceback (dernier appel le plus récent):
    Fichier "", ligne 1, dans <module>
    Fichier "", ligne 1, dans div_zéro
ZeroDivisionError: division par zéro

La fonction normale provoque une erreur similaire mais donne une trace plus précise car elle donne le nom de la fonction, div_zéro.

Syntaxe

Comme vous l'avez vu dans les sections précédentes, une forme lambda présente des distinctions syntaxiques par rapport à une fonction normale. En particulier, une fonction lambda présente les caractéristiques suivantes:

  • Il ne peut contenir que des expressions et ne peut inclure d’instruction dans son corps.
  • C'est écrit comme une seule ligne d'exécution.
  • Il ne supporte pas les annotations de type.
  • Il peut être immédiatement invoqué (IIFE).

Aucune déclaration

Une fonction lambda ne peut contenir aucune déclaration. Dans une fonction lambda, des déclarations comme revenir, passer, affirmer, ou élever va soulever une Erreur de syntaxe exception. Voici un exemple d’ajout de affirmer au corps d'un lambda:

>>>

>>> (lambda X: affirmer X == 2) (2)
  Fichier "", ligne 1
    (lambda X: affirmer X == 2) (2)
                    ^
Erreur de syntaxe: Syntaxe invalide

Cet exemple artificiel destiné à affirmer ce paramètre X avait une valeur de 2. Mais l'interprète identifie un Erreur de syntaxe lors de l'analyse du code qui implique l'instruction affirmer dans le corps de la lambda.

Expression unique

Contrairement à une fonction normale, une fonction Python lambda est une expression unique. Bien que, dans le corps d'un lambda, vous pouvez répartir l’expression sur plusieurs lignes à l’aide de parenthèses ou d’une chaîne multiligne, il ne reste plus qu’une expression:

>>>

>>> (lambda X:
... (X % 2 et 'impair' ou 'même')) (3)
'impair'

L'exemple ci-dessus renvoie la chaîne 'impair' quand l'argument lambda est impair, et 'même' quand l'argument est égal. Il s'étend sur deux lignes car il est contenu dans un ensemble de parenthèses, mais reste une expression unique.

Annotations de type

Si vous avez commencé à adopter les indicateurs de type, qui sont maintenant disponibles en Python, vous avez une autre bonne raison de préférer les fonctions normales aux fonctions Python lambda. Consultez la Vérification de type Python (Guide) pour en savoir plus sur les astuces de type et la vérification de type Python. Dans une fonction lambda, il n'y a pas d'équivalent pour ce qui suit:

def nom complet(premier: str, dernier: str) -> str:
    revenir F'first.title () last.title ()'

Toute erreur de type avec nom complet() peut être attrapé par des outils comme mypy ou bûcher, alors qu'un Erreur de syntaxe avec la fonction lambda équivalente est levée à l'exécution:

>>>

>>> lambda premier: str, dernier: str: premier.Titre() + "" + dernier.Titre() -> str
  Fichier "", ligne 1
    lambda premier: str, dernier: str: premier.Titre() + "" + dernier.Titre() -> str

ErreurDeSyntaxe: Syntaxe invalide

Comme essayer d’inclure une instruction dans un lambda, l’ajout d’une annotation de type entraîne immédiatement une Erreur de syntaxe à l'exécution.

IIFE

Vous avez déjà vu plusieurs exemples d’exécution de fonction immédiatement appelée:

>>>

>>> (lambda X: X * X) (3)
9

En dehors de l'interpréteur Python, cette fonctionnalité n'est probablement pas utilisée dans la pratique. C’est une conséquence directe du fait qu’une fonction lambda puisse être appelée telle qu’elle est définie. Par exemple, cela vous permet de passer la définition d’une expression lambda Python à une fonction d’ordre supérieur, telle que carte(), filtre(), ou functools.reduce (), ou à une fonction clé.

Arguments

Comme un objet de fonction normal défini avec def, Les expressions Python lambda prennent en charge les différentes manières de passer des arguments. Ceci comprend:

  • Arguments de position
  • Arguments nommés (parfois appelés arguments mot-clé)
  • Liste variable d'arguments (souvent appelée varargs)
  • Liste variable d'arguments de mots clés
  • Arguments contenant uniquement des mots-clés

Les exemples suivants illustrent les options disponibles pour passer des arguments aux expressions lambda:

>>>

>>> (lambda X, y, z: X + y + z) (1, 2, 3)
6
>>> (lambda X, y, z=3: X + y + z) (1, 2)
6
>>> (lambda X, y, z=3: X + y + z) (1, y=2)
6
>>> (lambda *args: somme(args)) (1,2,3)
6
>>> (lambda **Kwargs: somme(Kwargs.valeurs())) (un=1, deux=2, Trois=3)
6
>>> (lambda X, *, y=0, z=0: X + y + z) (1, y=2, z=3)
6

Décorateurs

En Python, un décorateur est l'implémentation d'un modèle qui permet d'ajouter un comportement à une fonction ou à une classe. Il est généralement exprimé avec le @décorateur syntaxe préfixant une fonction. Voici un exemple artificiel:

def un_décorateur(F):
    def enveloppements(*args):
        impression(F"Fonction d'appel"f .__ nom__'")
        revenir F(args)
    revenir enveloppements

@some_decorator
def fonction_décorée(X):
    impression(F"Avec argument"X'")

Dans l'exemple ci-dessus, un_décorateur () est une fonction qui ajoute un comportement à Fonction_décorée (), de sorte que l'invocation Fonction_décorée (2) résulte dans la sortie suivante

Fonction appelant 'colored_function'
Avec l'argument 'Python'

Fonction_décorée () seulement des impressions Avec l'argument 'Python', mais le décorateur ajoute un comportement supplémentaire qui imprime également Fonction appelant 'colored_function'.

Un décorateur peut être appliqué à un lambda. Bien qu’il ne soit pas possible de décorer un lambda avec le @décorateur syntaxe, un décorateur est juste une fonction, il peut donc appeler la fonction lambda:

    1 # Définir un décorateur
    2 def trace(F):
    3     def emballage(*args, **Kwargs):
    4         impression(F"[TRACE] func: f .__ nom__, args: args, kwargs: kwargs")
    5         revenir F(*args, **Kwargs)
    6 
    7     revenir emballage
    8 
    9 # Appliquer un décorateur à une fonction
dix @trace
11 def add_two(X):
12     revenir X + 2
13 
14 # Appel de la fonction décorée
15 add_two(3)
16 
17 # Appliquer un décorateur sur un lambda
18 impression((trace(lambda X: X ** 2)) (3))

add_two (), décoré avec @trace à la ligne 11, est invoqué avec argument 3 ligne 15. Par contre, sur la ligne 18, une fonction lambda est immédiatement impliquée et intégrée dans un appel à trace(), le décorateur. Lorsque vous exécutez le code ci-dessus, vous obtenez ce qui suit:

[TRACE]    func: add_two, args: (3,), kwargs: 
[TRACE]    func: , args: (3,), kwargs: 
9

Voyez comment, comme vous l’avez déjà vu, le nom de la fonction lambda apparaît sous la forme , tandis que add_two est clairement identifié pour la fonction normale.

Décorer la fonction lambda de cette manière pourrait être utile à des fins de débogage, éventuellement pour déboguer le comportement d'une fonction lambda utilisée dans le contexte d'une fonction d'ordre supérieur ou d'une fonction de clé. Voyons un exemple avec carte():

liste(carte(trace(lambda X: X*2), intervalle(3)))

Le premier argument de carte() est un lambda qui multiplie son argument par 2. Ce lambda est décoré avec trace(). Lorsqu'il est exécuté, l'exemple ci-dessus génère les éléments suivants:

[TRACE]    Appel  avec args (0,) et kwargs 
[TRACE]    Appel  avec args (1,) et kwargs 
[TRACE]    Appel  avec args (2,) et kwargs 
[0, 2, 4]

Le résultat [0, 2, 4] est une liste obtenue en multipliant chaque élément de gamme (3). Pour l'instant, considérez gamme (3) équivalent à la liste [0, 1, 2].

Vous serez exposé à carte() en plus de détails dans la carte.

Un lambda peut aussi être décorateur, mais ce n’est pas recommandé. Si vous avez besoin de le faire, consultez PEP 8, Recommandations de programmation.

Pour en savoir plus sur les décorateurs Python, consultez Apprêt sur les décorateurs Python.

Fermeture

Une fermeture est une fonction où chaque variable libre, tout sauf les paramètres, utilisés dans cette fonction est liée à une valeur spécifique définie dans la portée englobante de cette fonction. En effet, les fermetures définissent l'environnement dans lequel elles s'exécutent et peuvent donc être appelées de n'importe où.

Les concepts de lambda et de fermeture ne sont pas nécessairement liés, bien que les fonctions de lambda puissent être des fermetures de la même manière que les fonctions normales peuvent également être des fermetures. Certaines langues ont des constructions spéciales pour fermeture ou lambda (par exemple, Groovy avec un bloc de code anonyme en tant qu'objet Closure) ou une expression lambda (par exemple, expression Java Lambda avec une option limitée pour la fermeture).

Voici une fermeture construite avec une fonction Python normale:

    1 def outer_func(X):
    2     y = 4
    3     def inner_func(z):
    4         impression(F"x = X, y = yz = z")
    5         revenir X + y + z
    6     revenir inner_func
    7 
    8 pour je dans intervalle(3):
    9     fermeture = outer_func(je)
dix     impression(F"fermeture (i + 5) = fermeture (i + 5)")

outer_func () résultats inner_func (), une fonction imbriquée qui calcule la somme de trois arguments:

  • X est passé comme argument à outer_func ().
  • y est une variable locale à outer_func ().
  • z est un argument passé à inner_func ().

Pour tester le comportement de outer_func () et inner_func (), outer_func () est invoqué trois fois dans un pour boucle qui imprime ce qui suit:

x = 0, y = 4, z = 5
fermeture (5) = 9
x = 1, y = 4, z = 6
fermeture (6) = 11
x = 2, y = 4, z = 7
fermeture (7) = 13

À la ligne 9 du code, inner_func () retourné par l'invocation de outer_func () est lié au nom fermeture. Sur la ligne 5, inner_func () capture X et y parce qu'il a accès à son environnement d'intégration, de telle sorte que, lors de l'appel de la fermeture, il est capable de fonctionner sur les deux variables libres X et y.

De même, un lambda peut aussi être une fermeture. Voici le même exemple avec une fonction Python lambda:

    1 def outer_func(X):
    2     y = 4
    3     revenir lambda z: X + y + z
    4 
    5 pour je dans intervalle(3):
    6     fermeture = outer_func(je)
    7     impression(fermeture(je))

Lorsque vous exécutez le code ci-dessus, vous obtenez le résultat suivant:

fermeture (5) = 9
fermeture (6) = 11
fermeture (7) = 13

À la ligne 6, outer_func () retourne un lambda et l'assigne à la variable fermeture. Sur la ligne 3, le corps de la fonction lambda fait référence à X et y. La variable y est disponible au moment de la définition, alors que X est défini à l'exécution lorsque outer_func () est invoqué.

Dans cette situation, la fonction normale et le lambda se comportent de la même manière. Dans la section suivante, vous verrez une situation dans laquelle le comportement d’un lambda peut être trompeur en raison de son temps d’évaluation (temps de définition / exécution).

Temps d'évaluation

Dans certaines situations impliquant des boucles, le comportement d’une fonction Python lambda en tant que fermeture peut être contre-intuitif. Cela nécessite de comprendre quand des variables libres sont liées dans le contexte d'un lambda. Les exemples suivants montrent la différence entre une fonction régulière et un python lambda.

Testez le scénario d'abord en utilisant une fonction régulière:

>>>

    1 >>> def emballage(n):
    2 ...     def F():
    3 ...         impression(n)
    4 ...     revenir F
    5 ...
    6 >>> Nombres = 'un', 'deux', 'Trois'
    7 >>> funcs = []
    8 >>> pour n dans Nombres:
    9 ...     funcs.ajouter(emballage(n))
dix ...
11 >>> pour F dans funcs:
12 ...     F()
13 ...
14 un
15 deux
16 Trois

Dans une fonction normale, n est évalué au moment de la définition, à la ligne 9, lorsque la fonction est ajoutée à la liste: funcs.append (wrap (n)).

Maintenant, avec l'implémentation de la même logique avec une fonction lambda, observez le comportement inattendu:

>>>

    1 >>> Nombres = 'un', 'deux', 'Trois'
    2 >>> funcs = []
    3 >>> pour n dans Nombres:
    4 ...     funcs.ajouter(lambda: impression(n))
    5 ...
    6 >>> pour F dans funcs:
    7 ...     F()
    8 ...
    9 Trois
dix Trois
11 Trois

Le résultat inattendu se produit parce que la variable libre n, tel qu’implémenté, est lié au moment de l’exécution de l’expression lambda. La fonction Python lambda sur la ligne 4 est une fermeture qui capture n, une variable libre liée à l'exécution. Au moment de l'exécution, tout en appelant la fonction F à la ligne 7, la valeur de n est Trois.

Pour résoudre ce problème, vous pouvez affecter la variable libre au moment de la définition, comme suit:

>>>

    1 >>> Nombres = 'un', 'deux', 'Trois'
    2 >>> funcs = []
    3 >>> pour n dans Nombres:
    4 ...     funcs.ajouter(lambda n=n: impression(n))
    5 ...
    6 >>> pour F dans funcs:
    7 ...     F()
    8 ...
    9 un
dix deux
11 Trois

Une fonction Python lambda se comporte comme une fonction normale en ce qui concerne les arguments. Par conséquent, un paramètre lambda peut être initialisé avec une valeur par défaut: le paramètre n prend l'extérieur n comme valeur par défaut. La fonction Python lambda aurait pu être écrite en tant que lambda x = n: print (x) et ont le même résultat.

La fonction lambda Python est appelée sans aucun argument sur la ligne 7 et utilise la valeur par défaut n défini au moment de la définition.

Tester les Lambda

Les lambdas Python peuvent être testés de la même manière que les fonctions habituelles. Il est possible d’utiliser les deux Test de l'unité et doctest.

Test de l'unité

le Test de l'unité Le module gère les fonctions Python lambda de la même manière que les fonctions habituelles:

importation Test de l'unité

ajouter deux = lambda X: X + 2

classe LambdaTest(Test de l'unité.Cas de test):
    def test_add_two(soi):
        soi.affirmerEqual(ajouter deux(2), 4)

    def test_add_two_point_two(soi):
        soi.affirmerEqual(ajouter deux(2.2), 4.2)

    def test_add_three(soi):
        # Devrait échouer
        soi.affirmerEqual(ajouter deux(3), 6)

si __prénom__ == '__principale__':
    Test de l'unité.principale(verbosité=2)

LambdaTest définit un cas de test avec trois méthodes de test, chacune d’elles appliquant un scénario de test pour addtwo () mis en œuvre en tant que fonction lambda. L'exécution du fichier Python lambda_unittest.py cela contient LambdaTest produit ce qui suit:

$ python lambda_unittest.py
test_add_three (__main __. LambdaTest) ... ECHEC
test_add_two (__main __. LambdaTest) ... ok
test_add_two_point_two (__main __. LambdaTest) ... ok

=============================================== =====================
FAIL: test_add_three (__main __. LambdaTest)
-------------------------------------------------- --------------------
Traceback (dernier appel le plus récent):
        Fichier "lambda_unittest.py", ligne 18, dans test_add_three
                self.assertEqual (addtwo (3), 6)
AssertionError: 5! = 6

-------------------------------------------------- --------------------
A couru 3 tests en 0.001s

FAILED (échecs = 1)

Comme prévu, nous avons deux cas de test réussis et un échec pour test_add_three: le résultat est 5, mais le résultat attendu était 6. Cet échec est dû à une erreur intentionnelle dans le cas test. Changer le résultat attendu de 6 à 5 satisfera tous les tests pour LambdaTest.

doctest

le doctest module extrait le code interactif Python de docstring exécuter des tests. Bien que la syntaxe des fonctions Python lambda ne prenne pas en charge les fonctions standard. docstring, il est possible d’attribuer une chaîne au __doc__ élément d'un lambda nommé:

ajouter deux = lambda X: X + 2
ajouter deux.__doc__ = "" "Ajoutez 2 à un nombre.
                >>> addtwo (2)
                4
                >>> addtwo (2.2)
                4.2
                >>> addtwo (3) # devrait échouer
                6
                "" "

si __prénom__ == '__principale__':
    importation doctest
    doctest.testmod(verbeux=Vrai)

le doctest dans le commentaire de lambda addtwo () décrit les mêmes cas de test que dans la section précédente.

Lorsque vous exécutez les tests via doctest.testmod (), vous obtenez ce qui suit:

$ python lambda_doctest.py
En essayant:
                addtwo (2)
Attendant:
                4
D'accord
En essayant:
                addtwo (2.2)
Attendant:
                4.2
D'accord
En essayant:
                addtwo (3) # devrait échouer
Attendant:
                6
************************************************* *********************
Fichier "lambda_doctest.py", ligne 16, dans __main __. Addtwo
Exemple d'échec:
                addtwo (3) # devrait échouer
Attendu:
                6
Eu:
                5
1 items n'avaient pas de test:
                __principale__
************************************************* *********************
1 articles ont échoué:
            1 sur 3 dans __de __. Addtwo
3 tests en 2 éléments.
2 ont réussi et 1 a échoué.
*** Échec du test *** 1 échec.

Les résultats des tests ayant échoué de la même défaillance expliquée dans l'exécution des tests unitaires de la section précédente.

Vous pouvez ajouter un docstring à un lambda Python via une affectation à __doc__ documenter une fonction lambda. Bien que possible, la syntaxe Python s’adapte mieux à docstring pour les fonctions normales que les fonctions lambda.

Pour un aperçu complet des tests unitaires en Python, vous pouvez vous reporter à la rubrique Mise en route des tests en Python.

Abus d'expression lambda

Plusieurs exemples de cet article, s’ils sont écrits dans le contexte de code Python professionnel, peuvent être qualifiés d’abus.

Si vous essayez de surmonter quelque chose qu'une expression lambda ne prend pas en charge, c'est probablement le signe qu'une fonction normale conviendrait mieux. le docstring pour une expression lambda dans la section précédente est un bon exemple. Tenter de surmonter le fait qu'une fonction lambda de Python ne supporte pas les instructions est un autre drapeau rouge.

Les sections suivantes illustrent quelques exemples d’usages lambda à éviter. Ces exemples peuvent être des situations où, dans le contexte de Python lambda, le code présente le modèle suivant:

  • Il ne suit pas le guide de style Python (PEP 8)
  • C’est lourd et difficile à lire.
  • C’est inutilement malin au prix d’une lisibilité difficile.

Relever une exception

Essayer de lever une exception dans un Python lambda devrait vous faire réfléchir à deux fois. Il existe des moyens astucieux de le faire, mais il vaut mieux éviter même ce qui suit:

>>>

>>> def jeter(ex): élever ex
>>> (lambda: jeter(Exception('Quelque chose de terrible est arrivé'))) ()
Traceback (dernier appel le plus récent):
    Fichier "", ligne 1, dans <module>
    Fichier "", ligne 1, dans <lambda>
    Fichier "", ligne 1, dans jeter
Exception: Quelque chose de terrible est arrivé

Because a statement is not syntactically correct in a Python lambda body, the workaround in the example above consists of abstracting the statement call with a dedicated function throw(). Using this type of workaround should be avoided. If you encounter this type of code, you should consider refactoring the code to use a regular function.

Cryptic Style

As in any programming languages, you will find Python code that can be difficult to read because of the style used. Lambda functions, due to their conciseness, can be conducive to writing code that is difficult to read.

The following lambda example contains several bad style choices:

>>>

>>> (lambda _: liste(carte(lambda _: _ // 2, _)))([[[[1,2,3,4,5,6,7,8,9,dix])
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

The underscore (_) refers to a variable that you don’t need to refer to explicitly. But in this example, three _ refer to different variables. An initial upgrade to this lambda code could be to name the variables:

>>>

>>> (lambda some_list: liste(carte(lambda n: n // 2,
                                                                                                                                some_list)))([1,2,3,4,5,6,7,8,9,10])
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

Admittedly, it’s still difficult to read. By still taking advantage of a lambda, a regular function would go a long way to render this code more readable, spreading the logic over a few lines and function calls:

>>>

>>> def div_items(some_list):
                        div_by_two = lambda n: n // 2
                        return map(div_by_two, some_list)
>>> liste(div_items([[[[1,2,3,4,5,6,7,8,9,dix])))
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

This is still not optimal but shows you a possible path to make code, and Python lambda functions in particular, more readable. In Alternatives to Lambdas, you’ll learn to replace map() et lambda with list comprehensions or generator expressions. This will drastically improve the readability of the code.

Python Classes

You can but should not write class methods as Python lambda functions. The following example is perfectly legal Python code but exhibits unconventional Python code relying on lambda. For example, instead of implementing __str__ as a regular function, it uses a lambda. Similarly, marque et année are properties also implemented with lambda functions, instead of regular functions or decorators:

classe Car:
    """Car with methods as lambda functions."""
    def __init__(soi, marque, année):
        soi.marque = marque
        soi.année = année

    marque = propriété(lambda soi: getattr(soi, '_brand'),
                     lambda soi, valeur: setattr(soi, '_brand', valeur))

    année = propriété(lambda soi: getattr(soi, '_year'),
                    lambda soi, valeur: setattr(soi, '_year', valeur))

    __str__ = lambda soi: F'self.brand self.year'  # 1: error E731

    klaxonner = lambda soi: impression('Honk!')     # 2: error E731

Running a tool like flake8, a style guide enforcement tool, will display the following errors for __str__ et klaxonner:

E731 do not assign a lambda expression, use a def

Bien que flake8 doesn’t point out an issue for the usage of the Python lambda functions in the properties, they are difficult to read and prone to error because of the usage of multiple strings like '_brand' et '_year'.

Proper implementation of __str__ would be expected to be as follows:

def __str__(soi):
    revenir F'self.brand self.year'

marque would be written as follows:

@property
def marque(soi):
    revenir soi._brand

@brand.setter
def marque(soi, valeur):
    soi._brand = valeur

As a general rule, in the context of code written in Python, prefer regular functions over lambda expressions. Nonetheless, there are cases that benefit from lambda syntax, as you will see in the next section.

Appropriate Uses of Lambda Expressions

Lambdas in Python tend to be the subject of controversies. Some of the arguments against lambdas in Python are:

  • Issues with readability
  • The imposition of a functional way of thinking
  • Heavy syntax with the lambda mot-clé

Despite the heated debates questioning the mere existence of this feature in Python, lambda functions have properties that sometimes provide value to the Python language and to developers.

The following examples illustrate scenarios where the use of lambda functions is not only suitable but encouraged in Python code.

Classic Functional Constructs

Lambda functions are regularly used with the built-in functions map() et filter(), aussi bien que functools.reduce(), exposed in the module functools. The following three examples are respective illustrations of using those functions with lambda expressions as companions:

>>>

>>> liste(carte(lambda X: X.plus haut(), [[[['cat', 'dog', 'cow']))
['CAT', 'DOG', 'COW']
>>> liste(filtre(lambda X: 'o' dans X, [[[['cat', 'dog', 'cow']))
['dog', 'cow']
>>> de functools importation réduire
>>> réduire(lambda acc, X: F'acc    | x', [[[['cat', 'dog', 'cow'])
'cat | dog | cow'

You may have to read code resembling the examples above, albeit with more relevant data. For that reason, it’s important to recognize those constructs. Nevertheless, those constructs have equivalent alternatives that are considered more Pythonic. In Alternatives to Lambdas, you’ll learn how to convert higher-order functions and their accompanying lambdas into other more idiomatic forms.

Key Functions

Key functions in Python are higher-order functions that take a parameter clé as a named argument. clé receives a function that can be a lambda. This function directly influences the algorithm driven by the key function itself. Here are some key functions:

  • sort(): list method
  • sorted(), min(), max(): fonctions intégrées
  • nlargest() et nsmallest(): in the Heap queue algorithm module heapq

Imagine that you want to sort a list of IDs represented as strings. Each ID is the concatenation of the string identifiant and a number. Sorting this list with the built-in function sorted(), by default, uses a lexicographic order as the elements in the list are strings.

To influence the sorting execution, you can assign a lambda to the named argument clé, such that the sorting will use the number associated with the ID:

>>>

>>> ids = [[[['id1', 'id2', 'id30', 'id3', 'id22', 'id100']
>>> impression(triés(ids)) # Lexicographic sort
['id1', 'id2', 'id30', 'id3', 'id22', 'id100']
>>> sorted_ids = triés(ids, clé=lambda X: int(X[[[[2:])) # Integer sort
>>> impression(sorted_ids)
['id1', 'id2', 'id3', 'id22', 'id30', 'id100']

UI Frameworks

UI frameworks like Tkinter, wxPython, or .NET Windows Forms with IronPython take advantage of lambda functions for mapping actions in response to UI events.

The naive Tkinter program below demonstrates the usage of a lambda assigned to the command of the Reverse button:

importation tkinter comme tk
importation sys

la fenêtre = tk.Tk()
la fenêtre.grid_columnconfigure(0, poids=1)
la fenêtre.Titre("Lambda")
la fenêtre.géométrie("300x100")
étiquette = tk.Étiquette(la fenêtre, texte="Lambda Calculus")
étiquette.la grille(colonne=0, rangée=0)
bouton = tk.Bouton(
    la fenêtre,
    texte="Reverse",
    commander=lambda: étiquette.configurer(texte=étiquette.cget("text")[::[::[::[::-1]),
)
bouton.la grille(colonne=0, rangée=1)
la fenêtre.mainloop()

Clicking the button Reverse fires an event that triggers the lambda function, changing the label from Lambda Calculus à suluclaC adbmaL*:

Animated TkInter Windows demonstrating the action of the button to the text

Both wxPython and IronPython on the .NET platform share a similar approach for handling events. Notez que lambda is one way to handle firing events, but a function may be used for the same purpose. It ends up being self-contained and less verbose to use a lambda when the amount of code needed is very short.

To explore wxPython, check out How to Build a Python GUI Application With wxPython.

Interprète python

When you’re playing with Python code in the interactive interpreter, Python lambda functions are often a blessing. It’s easy to craft a quick one-liner function to explore some snippets of code that will never see the light of day outside of the interpreter. The lambdas written in the interpreter, for the sake of speedy discovery, are like scrap paper that you can throw away after use.

timeit

In the same spirit as the experimentation in the Python interpreter, the module timeit provides functions to time small code fragments. timeit.timeit() in particular can be called directly, passing some Python code in a string. Here’s an example:

>>>

>>> de timeit importation timeit
>>> timeit("factorial(999)", "from math import factorial", nombre=dix)
0.0013087529951008037

When the statement is passed as a string, timeit() needs the full context. In the example above, this is provided by the second argument that sets up the environment needed by the main function to be timed. Not doing so would raise a NameError exception.

Another approach is to use a lambda:

>>>

>>> de math importation factoriel
>>> timeit(lambda: factoriel(999), nombre=dix)
0.0012704220062005334

This solution is cleaner, more readable, and quicker to type in the interpreter. Although the execution time was slightly less for the lambda version, executing the functions again may show a slight advantage for the chaîne version. The execution time of the installer is excluded from the overall execution time and shouldn’t have any impact on the result.

Monkey Patching

For testing, it’s sometimes necessary to rely on repeatable results, even if during the normal execution of a given software, the corresponding results are expected to differ, or even be totally random.

Let’s say you want to test a function that, at runtime, handles random values. But, during the testing execution, you need to assert against predictable values in a repeatable manner. The following example shows how, with a lambda function, monkey patching can help you:

de contextlib importation contextmanager
importation secrets

def gen_token():
    """Generate a random token."""
    revenir F'TOKEN_secrets.token_hex(8)'

@contextmanager
def mock_token():
    """Context manager to monkey patch the secrets.token_hex
                function during testing.
                """
    default_token_hex = secrets.token_hex
    secrets.token_hex = lambda _: 'feedfacecafebeef'
    rendement
    secrets.token_hex = default_token_hex

def test_gen_key():
    """Test the random token."""
    avec mock_token():
        affirmer gen_token() == F"TOKEN_'feedfacecafebeef'"

test_gen_key()

A context manager helps with insulating the operation of monkey patching a function from the standard library (secrets, in this example). The lambda function assigned to secrets.token_hex() substitutes the default behavior by returning a static value.

This allows testing any function depending on token_hex() in a predictable fashion. Prior to exiting from the context manager, the default behavior of token_hex() is reestablished to eliminate any unexpected side effects that would affect other areas of the testing that may depend on the default behavior of token_hex().

Unit test frameworks like unittest et pytest take this concept to a higher level of sophistication.

Avec pytest, still using a lambda function, the same example becomes more elegant and concise :

importation secrets

def gen_token():
    revenir F'TOKEN_secrets.token_hex(8)'

def test_gen_key(monkeypatch):
    monkeypatch.setattr('secrets.token_hex', lambda _: 'feedfacecafebeef')
    affirmer gen_token() == F"TOKEN_'feedfacecafebeef'"

With the pytest monkeypatch fixture, secrets.token_hex() is overwritten with a lambda that will return a deterministic value, feedfacecafebeef, allowing to validate the test. The pytest monkeypatch fixture allows you to control the scope of the override. In the example above, invoking secrets.token_hex() in subsequent tests, without using monkey patching, would execute the normal implementation of this function.

Executing the pytest test gives the following result:

$ pytest test_token.py -v
============================= test session starts ==============================
platform linux -- Python 3.7.2, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
cachedir: .pytest_cache
rootdir: /home/andre/AB/tools/bpython, inifile:
collected 1 item

test_token.py::test_gen_key PASSED                                       [100%]

=========================== 1 passed in 0.01 seconds ===========================

The test passes as we validated that the gen_token() was exercised, and the results were the expected ones in the context of the test.

Alternatives to Lambdas

While there are great reasons to use lambda, there are instances where its use is frowned upon. So what are the alternatives?

Higher-order functions like map(), filter(), et functools.reduce() can be converted to more elegant forms with slight twists of creativity, in particular with list comprehensions or generator expressions.

Watch Using List Comprehensions Effectively to learn more about list comprehensions.

Map

The built-in function map() takes a function as a first argument and applies it to each of the elements of its second argument, an itérable. Examples of iterables are strings, lists, and tuples. For more information on iterables and iterators, check out Iterables and Iterators.

map() returns an iterator corresponding to the transformed collection. As an example, if you wanted to transform a list of strings to a new list with each string capitalized, you could use map(), as follows:

>>>

>>> liste(carte(lambda X: X.capitaliser(), [[[['cat', 'dog', 'cow']))
['Cat', 'Dog', 'Cow']

You need to invoke list() to convert the iterator returned by map() into an expanded list that can be displayed in the Python shell interpreter.

Using a list comprehension eliminates the need for defining and invoking the lambda function:

>>>

>>> [[[[X.capitaliser() pour X dans [[[['cat', 'dog', 'cow']]
['Cat', 'Dog', 'Cow']

Filtre

The built-in function filter(), another classic functional construct, can be converted into a list comprehension. It takes a predicate as a first argument and an iterable as a second argument. It builds an iterator containing all the elements of the initial collection that satisfies the predicate function. Here’s an example that filters all the even numbers in a given list of integers:

>>>

>>> même = lambda X: X%2 == 0
>>> liste(filtre(même, intervalle(11)))
[0, 2, 4, 6, 8, 10]

Notez que filter() returns an iterator, hence the need to invoke the built-in type liste that constructs a list given an iterator.

The implementation leveraging the list comprehension construct gives the following:

>>>

>>> [[[[X pour X dans intervalle(11) si X%2 == 0]
[0, 2, 4, 6, 8, 10]

Réduire

Since Python 3, reduce() has bgone from a built-in function to a functools module function. Comme map() et filter(), its first two arguments are respectively a function and an iterable. It may also take an initializer as a third argument that is used as the initial value of the resulting accumulator. For each element of the iterable, reduce() applies the function and accumulates the result that is returned when the iterable is exhausted.

To apply reduce() to a list of pairs and calculate the sum of the first item of each pair, you could write this:

>>>

>>> importation functools
>>> paires = [([([([(1, 'a'), (2, 'b'), (3, 'c')]
>>> functools.réduire(lambda acc, paire: acc + paire[[[[0], paires, 0)
6

A more idiomatic approach using a generator expression, as an argument to sum() in the example, is the following:

>>>

>>> paires = [([([([(1, 'a'), (2, 'b'), (3, 'c')]
>>> somme(X[[[[0] pour X dans paires)
6

A slightly different and possibly cleaner solution removes the need to explicitly access the first element of the pair and instead use unpacking:

>>>

>>> paires = [([([([(1, 'a'), (2, 'b'), (3, 'c')]
>>> somme(X pour X, _ dans paires)
6

The use of underscore (_) is a Python convention indicating that you can ignore the second value of the pair.

sum() takes a unique argument, so the generator expression does not need to be in parentheses.

Are Lambdas Pythonic or Not?

PEP 8, which is the style guide for Python code, reads:

Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier. (La source)

This strongly discourages using lambda bound to an identifier, mainly where functions should be used and have more benefits. PEP 8 does not mention other usages of lambda. As you have seen in the previous sections, lambda functions may certainly have good uses, although they are limited.

A possible way to answer the question is that lambda functions are perfectly Pythonic if there is nothing more Pythonic available. I’m staying away from defining what “Pythonic” means, leaving you with the definition that best suits your mindset, as well as your personal or your team’s coding style.

Beyond the narrow scope of Python lambda, How to Write Beautiful Python Code With PEP 8 is a great resource that you may want to check out regarding code style in Python.

Conclusion

You now know how to use Python lambda functions and can:

  • Write Python lambdas and use anonymous functions
  • Choose wisely between lambdas or normal Python functions
  • Avoid excessive use of lambdas
  • Use lambdas with higher-order functions or Python key functions

If you have a penchant for mathematics, you may have some fun exploring the fascinating world of lambda calculus.

Python lambdas are like salt. A pinch in your spam, ham, and eggs will enhance the flavors, but too much will spoil the dish.