Plus préférable: fonctions lambda ou fonctions imbriquées ( def
)?
Il y a un avantage à utiliser un lambda par rapport à une fonction régulière: ils sont créés dans une expression.
Il y a plusieurs inconvénients:
- pas de nom (juste
'<lambda>'
)
- pas de docstrings
- pas d'annotations
- pas de déclarations complexes
Ils sont également tous deux du même type d'objet. Pour ces raisons, je préfère généralement créer des fonctions avec le def
mot - clé plutôt qu'avec lambdas.
Premier point - ils sont du même type d'objet
Un lambda donne le même type d'objet qu'une fonction régulière
>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
...
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True
Puisque les lambdas sont des fonctions, ce sont des objets de première classe.
Les lambdas et les fonctions:
- peut être passé en argument (identique à une fonction régulière)
- lorsqu'il est créé dans une fonction externe, devient une fermeture sur les sections locales de ces fonctions externes
Mais les lambdas manquent, par défaut, de certaines choses que les fonctions obtiennent via la syntaxe de définition de fonction complète.
Un lamba __name__
est'<lambda>'
Les lambdas sont des fonctions anonymes, après tout, donc ils ne connaissent pas leur propre nom.
>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'
Ainsi, les lambda ne peuvent pas être recherchés par programme dans leur espace de noms.
Cela limite certaines choses. Par exemple, foo
peut être recherché avec un code sérialisé, alors que l
ne peut pas:
>>> import pickle
>>> pickle.loads(pickle.dumps(l))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function <lambda> at 0x7fbbc0464e18>:
attribute lookup <lambda> on __main__ failed
Nous pouvons très bien chercher foo
- car il connaît son propre nom:
>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>
Les lambdas n'ont pas d'annotations et pas de docstring
Fondamentalement, les lambdas ne sont pas documentés. Réécrivons foo
pour être mieux documenté:
def foo() -> int:
"""a nullary function, returns 0 every time"""
return 0
Maintenant, foo a de la documentation:
>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:
foo() -> int
a nullary function, returns 0 every time
Alors que nous n'avons pas le même mécanisme pour donner les mêmes informations aux lambdas:
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda (...)
Mais nous pouvons les pirater sur:
>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda ) -> in
nullary -> 0
Mais il y a probablement une erreur qui gâche la sortie de l'aide, cependant.
Lambdas ne peut renvoyer qu'une expression
Lambdas ne peut pas renvoyer d'instructions complexes, uniquement des expressions.
>>> lambda: if True: 0
File "<stdin>", line 1
lambda: if True: 0
^
SyntaxError: invalid syntax
Les expressions peuvent certes être assez complexes, et si vous essayez très dur, vous pouvez probablement accomplir la même chose avec un lambda, mais la complexité supplémentaire est plus un inconvénient à l'écriture de code clair.
Nous utilisons Python pour plus de clarté et de maintenabilité. La surutilisation des lambdas peut agir contre cela.
Le seul avantage pour les lambdas: peut être créé en une seule expression
C'est le seul avantage possible. Puisque vous pouvez créer un lambda avec une expression, vous pouvez le créer à l'intérieur d'un appel de fonction.
La création d'une fonction à l'intérieur d'un appel de fonction évite la recherche de nom (peu coûteuse) par rapport à celle créée ailleurs.
Cependant, étant donné que Python est strictement évalué, il n'y a pas d'autre gain de performances que d'éviter la recherche de nom.
Pour une expression très simple, je pourrais choisir un lambda.
J'ai également tendance à utiliser des lambdas lorsque je fais du Python interactif, pour éviter plusieurs lignes quand on fera l'affaire. J'utilise le type de format de code suivant lorsque je veux passer un argument à un constructeur lors de l'appel timeit.repeat
:
import timeit
def return_nullary_lambda(return_value=0):
return lambda: return_value
def return_nullary_function(return_value=0):
def nullary_fn():
return return_value
return nullary_fn
Et maintenant:
>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304
Je crois que la légère différence de temps ci-dessus peut être attribuée à la recherche de nom dans return_nullary_function
- notez que c'est très négligeable.
Conclusion
Les lambdas sont parfaits pour les situations informelles où vous souhaitez minimiser les lignes de code en faveur de la création d'un point singulier.
Les lambdas sont mauvais pour les situations plus formelles où vous avez besoin de clarté pour les éditeurs de code qui viendront plus tard, en particulier dans les cas où ils ne sont pas triviaux.
Nous savons que nous sommes censés donner de bons noms à nos objets. Comment pouvons-nous le faire quand l'objet n'a pas nom?
Pour toutes ces raisons, je préfère généralement créer des fonctions avec def
plutôt qu'avec lambda
.
lambda
, mais je ne suis pas d'accord pour dire que c'est "très rare", c'est courant pour les fonctions clés àsorted
ouitertools.groupby
etc., par exemplesorted(['a1', 'b0'], key= lambda x: int(x[1]))