web-dev-qa-db-fra.com

Quel est le plus préférable d'utiliser en Python: fonctions lambda ou fonctions imbriquées ('def')?

J'utilise principalement des fonctions lambda mais parfois des fonctions imbriquées qui semblent fournir le même comportement. 

Voici quelques exemples triviaux où ils font fonctionnellement la même chose si l'un ou l'autre était trouvé dans une autre fonction:

Fonction lambda

>>> a = lambda x : 1 + x
>>> a(5)
6

Fonction imbriquée

>>> def b(x): return 1 + x

>>> b(5)
6

Y a-t-il des avantages à utiliser l'un par rapport à l'autre? (Performance? Lisibilité? Limitations? Cohérence? Etc.) 

Est-ce même important? Si ce n'est pas le cas, cela viole-t-il le principe Pythonic: 

“Il devrait y avoir un - et de préférence un seul - moyen évident de le faire” .

83
Ray Vega

Si vous devez attribuer un nom à lambda, utilisez plutôt un def. defs sont simplement du sucre syntaxique pour une assignation, donc le résultat est le même, et ils sont beaucoup plus souples et lisibles.

lambdas peut être utilisé pour utiliser une fois, jette des fonctions qui n'auront pas de nom.

Cependant, ce cas d'utilisation est très rare. Il est rarement nécessaire de faire passer des objets fonction non nommés.

Les fonctions intégrées map() et filter() ont besoin d'objets fonction, mais la liste des compréhensions et les expressions du générateur sont généralement plus lisibles que ces fonctions et peuvent couvrir tous les cas d'utilisation, sans nécessiter de lambda. 

Pour les cas où vous avez vraiment besoin d'un petit objet fonction, vous devriez utiliser les fonctions du module operator, comme operator.add au lieu de lambda x, y: x + y.

Si vous avez encore besoin d'une variable lambda non couverte, vous pouvez envisager d'écrire une variable def, juste pour être plus lisible. Si la fonction est plus complexe que celle du module operator, une def est probablement meilleure. 

Ainsi, dans le monde réel, les cas d'utilisation lambda sont très rares.

87
nosklo

En pratique, il y a deux différences pour moi:

Le premier concerne ce qu’ils font et ce qu’ils retournent:

  • def est un mot clé qui ne renvoie rien et crée un "nom" dans l'espace de noms local.

  • lambda est un mot clé qui renvoie un objet fonction et ne crée pas de "nom" dans l'espace de noms local.

Par conséquent, si vous avez besoin d'appeler une fonction qui prend un objet fonction, la seule façon de le faire dans une ligne de code python est d'utiliser un lambda. Il n'y a pas d'équivalent avec def.

Dans certains cadres, cela est en fait assez courant; par exemple, j'utilise Twisted beaucoup, et donc faire quelque chose comme

d.addCallback(lambda result: setattr(self, _someVariable, result))

est assez courant et plus concis avec les lambdas.

La deuxième différence concerne ce que la fonction réelle est autorisée à faire.

  • Une fonction définie avec 'def' peut contenir n'importe quel code python
  • Une fonction définie avec 'lambda' doit être évaluée comme une expression et ne peut donc pas contenir d'instructions telles que print, import, raise, ... 

Par exemple,

def p(x): print x

fonctionne comme prévu, tandis que

lambda x: print x

est une SyntaxError.

Bien sûr, il existe des solutions de contournement - remplacez print par sys.stdout.write ou import par __import__. Mais généralement, il vaut mieux utiliser une fonction dans ce cas.

27

Dans cette interview, / Guido van Rossum dit qu'il souhaiterait ne pas avoir laissé "lambda" dans Python:

"Q. Quelle caractéristique de Python vous satisfait le moins?

Parfois, j'ai été trop rapide dans l'acceptation de contributions et je me suis rendu compte plus tard que c'était une erreur. Certaines fonctionnalités de programmation, telles que les fonctions lambda, en sont un exemple. lambda est un mot clé qui vous permet de créer une petite fonction anonyme. Les fonctions intégrées telles que mapper, filtrer et réduire exécutent une fonction sur un type de séquence, tel qu'une liste.

En pratique, cela ne s'est pas bien passé. Python n'a que deux portées: locale et globale. Cela rend l’écriture des fonctions lambda pénible, car vous voulez souvent accéder aux variables de l’étendue où lambda a été défini, mais vous ne pouvez pas en raison des deux étendues. Il y a un moyen de contourner cela, mais c'est une sorte de kludge. Il semble souvent beaucoup plus facile en Python d’utiliser une boucle for au lieu de jouer avec les fonctions lambda. map and friends ne fonctionnent bien que s’il existe déjà une fonction intégrée qui fait ce que vous voulez.

IMHO, Iambdas peut être pratique parfois, mais sont généralement pratiques au détriment de la lisibilité. Pouvez-vous me dire ce que cela fait:

str(reduce(lambda x,y:x+y,map(lambda x:x**x,range(1,1001))))[-10:]

Je l'ai écrit et il m'a fallu une minute pour le comprendre. Ceci est du projet Euler - je ne dirai pas quel problème parce que je déteste les spoilers, mais il fonctionne en 0.124 secondes :)

19
Chris Lawlor

Pour n = 1000, voici le temps d'appeler une fonction vs un lambda:

In [11]: def f(a, b):
             return a * b

In [12]: g = lambda x, y: x * y

In [13]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    f(a, b)
   ....:
100 loops, best of 3: 285 ms per loop

In [14]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    g(a, b)
   ....:
100 loops, best of 3: 298 ms per loop

In [15]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    (lambda x, y: x * y)(a, b)
   ....:
100 loops, best of 3: 462 ms per loop
9
Andy Hayden

Je suis d'accord avec le conseil de nosklo: si vous devez donner un nom à la fonction, utilisez def. Je réserve des fonctions lambda pour les cas où je ne fais que passer un court extrait de code à une autre fonction, par exemple:

a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )
6
Dan Lenski

Performance:

Créer une fonction avec lambda est légèrement plus rapide que de le créer avec def. La différence est due à la création d'une entrée de nom dans la table des sections locales par def. La fonction résultante a la même vitesse d'exécution.


Lisibilité:

Les fonctions Lambda sont un peu moins lisibles pour la plupart des utilisateurs de Python, mais aussi beaucoup plus concises dans certaines circonstances. Envisagez de passer d'une routine non fonctionnelle à fonctionnelle:

# Using non-functional version.

heading(math.sqrt(v.x * v.x + v.y * v.y), math.atan(v.y / v.x))

# Using lambda with functional version.

fheading(v, lambda v: math.sqrt(v.x * v.x + v.y * v.y), lambda v: math.atan(v.y / v.x))

# Using def with functional version.

def size(v):
    return math.sqrt(v.x * v.x + v.y * v.y)

def direction(v):
    return math.atan(v.y / v.x)

deal_with_headings(v, size, direction)

Comme vous pouvez le constater, la version lambda est plus courte et "simple" en ce sens qu'il suffit d'ajouter lambda v: à la version d'origine non fonctionnelle pour la convertir en version fonctionnelle. C'est aussi beaucoup plus concis. Mais souvenez-vous que beaucoup d'utilisateurs de Python seront déroutés par la syntaxe lambda. Ainsi, ce que vous perdez en longueur et en complexité peut être récupéré dans la confusion par d'autres codeurs.


Limites:

  • Les fonctions lambda ne peuvent être utilisées qu'une seule fois, sauf si elles sont affectées à un nom de variable.
  • Les fonctions lambda affectées à des noms de variables n’ont aucun avantage sur les fonctions def.
  • Les fonctions lambda peuvent être difficiles, voire impossibles à maîtriser.
  • Les noms des fonctions def doivent être choisis avec soin pour être raisonnablement descriptifs et uniques ou du moins non utilisés dans leur portée.

Cohérence:

Python évite généralement les conventions de programmation fonctionnelle en faveur de la sémantique procédurale et de la sémantique objective. L'opérateur lambda contraste directement avec ce biais. De plus, la fonction def ajoute de la diversité à votre syntaxe, au lieu de lambda déjà répandu. Certains considéreraient cela moins cohérent.


Fonctions préexistantes:

Comme l'ont noté d'autres personnes, de nombreuses utilisations de lambda sur le terrain peuvent être remplacées par des membres de operator ou d'autres modules. Par exemple:

do_something(x, y, lambda x, y: x + y)
do_something(x, y, operator.add)

L'utilisation de la fonction préexistante peut rendre le code plus lisible dans de nombreux cas.


Le principe pythonique: "Il devrait y avoir un moyen, et de préférence un seul, évident de le faire."

Cela ressemble à la doctrine de la { source unique de vérité }. Malheureusement, le principe de la seule façon évidente de le faire a toujours été davantage une aspiration nostalgique à Python qu’un véritable principe directeur. Considérez les très puissantes connaissances sur les tableaux en Python. Elles sont fonctionnellement équivalentes aux fonctions map et filter:

[e for e in some_array if some_condition(e)]
filter(some_array, some_condition)

lambda et def sont les mêmes.

C'est une question d'opinion, mais je dirais que tout ce qui, dans le langage Python, est destiné à un usage général et qui ne casse manifestement rien, est suffisamment "Pythonic".

5
Pi Marillion

Tout en étant d’accord avec les autres réponses, il est parfois plus lisible. Voici un exemple où lambda est pratique, dans un cas d'utilisation, je rencontre toujours une dimension N defaultdict .
Voici un exemple:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
d['Foo']['Bar'].append(something)

Je le trouve plus lisible que de créer une def pour la deuxième dimension. Ceci est encore plus significatif pour les dimensions supérieures.

3
Jonathan

Lambda a toujours été utilisé principalement pour les fonctions de rappel simples et pour les fonctions map, réduire, filtrer, qui nécessitent une fonction en tant qu'argument. Avec les compréhensions de liste devenant la norme, et les ajouts autorisés si comme dans:

x = [f for f in range(1, 40) if f % 2]

il est difficile d'imaginer un cas réel d'utilisation quotidienne de lambda. En conséquence, je dirais, évitez lambda et créez des fonctions imbriquées.

3
apg

Une limitation importante des lambdas est qu'ils ne peuvent contenir rien d'autre qu'une expression. Il est presque impossible pour une expression lambda de produire autre chose que des effets secondaires triviaux, car elle ne peut avoir un corps aussi riche que la fonction def 'ed.

Cela étant dit, Lua a influencé mon style de programmation en vue d’une utilisation intensive des fonctions anonymes, et j’en ai jeté mon code. En plus de cela, j'ai tendance à penser à map/réduire en tant qu'opérateurs abstraits d'une manière que je ne considère pas comme des compréhensions de liste ou des générateurs, presque comme si je reportais explicitement une décision d'implémentation en utilisant ces opérateurs. 

Edit: Ceci est une question assez ancienne, et mes opinions sur la question ont changé, un peu.

Tout d’abord, je suis fortement contre l’attribution d’une expression lambda à une variable; comme python a une syntaxe spéciale juste pour ça (indice, def). En plus de cela, de nombreuses utilisations de lambda, même lorsqu'elles ne reçoivent pas de nom, ont des implémentations prédéfinies (et plus efficaces). Par exemple, l'exemple en question peut être abrégé en (1).__add__, sans qu'il soit nécessaire de l'envelopper dans une variable lambda ou def. De nombreuses autres utilisations courantes peuvent être satisfaites avec une combinaison des modules operator, itertools et functools.

Une utilisation pour lambdas que j'ai trouvée ... est dans les messages de débogage.

Puisque lambdas peut être évalué paresseusement, vous pouvez avoir un code comme celui-ci:

log.debug(lambda: "this is my message: %r" % (some_data,))

au lieu de éventuellement cher:

log.debug("this is my message: %r" % (some_data,))

qui traite la chaîne de format même si l'appel de débogage ne produit pas de sortie en raison du niveau de journalisation actuel.

Bien entendu, pour fonctionner correctement, le module de journalisation utilisé doit prendre en charge lambdas en tant que "paramètres paresseux" (comme le fait mon module de journalisation).

La même idée peut être appliquée à tout autre cas d'évaluation paresseuse pour la création de valeur de contenu à la demande.

Par exemple, cet opérateur ternaire personnalisé:

def mif(condition, when_true, when_false):
    if condition:
         return when_true()
    else:
         return when_false()

mif(a < b, lambda: a + a, lambda: b + b)

au lieu de:

def mif(condition, when_true, when_false):
    if condition:
         return when_true
    else:
         return when_false

mif(a < b, a + a, b + b)

avec lambdas, seule l'expression sélectionnée par la condition sera évaluée, sans lambdas, les deux seront évalués.

Bien sûr, vous pouvez simplement utiliser des fonctions à la place de lambdas, mais pour les expressions courtes, les mots lambdas sont (c) plus minces.

2
Glushiator

Plus préférables: 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) et plusieurs inconvénients. Pour cette raison, je préfère créer des fonctions avec le mot clé def plutôt qu'avec lambdas.

Premier point - ils sont le même type d'objet

Un lambda donne le même type d'objet qu'une fonction normale

>>> 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.

Lambdas et fonctions:

  • peut être passé comme argument (identique à une fonction normale)
  • une fois créé dans une fonction externe, il devient une fermeture par rapport aux sections locales de ces fonctions externes

Mais les lambdas manquent, par défaut, de choses que les fonctions obtiennent via la syntaxe de définition complète.

Le __name__ d'un lamba est '<lambda>'

Les Lambda 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 rechercher foo parfaitement - car il connaît son propre nom:

>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>

Les Lambda n'ont ni annotations ni 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

Considérant 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 on peut les pirater:

>>> 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 perturbe la sortie de l'aide, cependant.

Lambdas ne peut que renvoyer une expression

Lambdas ne peut pas renvoyer des instructions complexes, mais 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 difficile, vous pourrez probablement accomplir la même chose avec un lambda, mais la complexité ajoutée nuit davantage à la rédaction de code clair. 

Nous utilisons Python pour la clarté et la maintenabilité. La surutilisation de lambdas peut jouer contre cela.

Le seulement avantage pour lambdas: peut être créé dans une seule expression

C'est le seul avantage possible. Comme vous pouvez créer un lambda avec une expression, vous pouvez le créer dans un appel de fonction. 

La création d'une fonction dans un appel de fonction évite la recherche de noms (peu coûteuse) par rapport à celle créée ailleurs. 

Cependant, étant donné que Python est strictement évalué, il n’ya aucun autre avantage en termes de performances à éviter d’éviter la recherche de nom.

Pour une expression très simple, je pourrais choisir un lambda.

J'ai aussi tendance à utiliser lambdas lors de l'utilisation de Python interactif, pour éviter les lignes multiples quand on le fait. J'utilise le type de format de code suivant lorsque je souhaite transmettre un argument à un constructeur lors de l'appel de 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 lambda conviennent aux situations informelles dans lesquelles vous souhaitez minimiser les lignes de code au lieu de faire un point singulier.

Les lambda sont mauvaises 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 supposés donner à nos objets de bons noms. Comment pouvons-nous le faire lorsque l'objet a no name?

Pour toutes ces raisons, je préfère créer des fonctions avec def au lieu de lambda.

2
Aaron Hall

Si vous souhaitez simplement affecter le lambda à une variable de la portée locale, vous pouvez également utiliser le paramètre def, car il est plus lisible et peut être développé plus facilement à l'avenir:

fun = lambda a, b: a ** b # a pointless use of lambda
map(fun, someList)

ou

def fun(a, b): return a ** b # more readable
map(fun, someList)
1
too much php
  • Temps de calcul.
  • Fonction sans nom.
  • Pour atteindre une fonction et de nombreuses utilisations.

Considérant un exemple simple,

# CREATE ONE FUNCTION AND USE IT TO PERFORM MANY OPERATIONS ON SAME TYPE OF DATA STRUCTURE.
def variousUse(a,b=lambda x:x[0]):
    return [b(i) for i in a]

dummyList = [(0,1,2,3),(4,5,6,7),(78,45,23,43)]
variousUse(dummyList)                           # extract first element
variousUse(dummyList,lambda x:[x[0],x[2],x[3]]) # extract specific indexed element
variousUse(dummyList,lambda x:x[0]+x[2])        # add specific elements
variousUse(dummyList,lambda x:x[0]*x[2])        # multiply specific elements
0
bhargav patel

Je suis d'accord avec nosklo. En passant, même avec un utilisez une fois, jetez fonction, la plupart du temps, vous voulez simplement utiliser quelque chose du module opérateur.

PAR EXEMPLE : 

Vous avez une fonction avec cette signature: myFunction (data, callback function).

Vous voulez passer une fonction qui ajoute 2 éléments.

Utiliser lambda:

myFunction(data, (lambda x, y : x + y))

La façon pythonique:

import operator
myFunction(data, operator.add)

Bien entendu, il s’agit d’un exemple simple, mais le module opérateur contient de nombreux éléments, notamment les paramètres de réglage et d’obtention des éléments pour list et dict. Vraiment cool.

0
e-satis