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

StackOverflow https://stackoverflow.com/questions/134626

Question

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

Voici quelques exemples triviaux où, fonctionnellement, ils font la même chose s'ils ont été trouvés 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? (Performances? Lisibilité? Limites? Cohérence? Etc.)

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

  

& # 8220; Il devrait y avoir un & # 8212; et de préférence un seul & # 8212; ; moyen évident de le faire & # 8221; .

Était-ce utile?

La solution

Si vous devez attribuer le lambda à un nom, utilisez plutôt un def . Les def ne sont que du sucre syntaxique pour une assignation. Le résultat est donc identique, ils sont beaucoup plus souples et lisibles.

Les

lambda peuvent être utilisés pour utiliser des fonctions uniques, jeter qui n'auront pas de nom.

Cependant, ce cas d'utilisation est très rare. Vous devez rarement faire passer des objets fonction non nommés.

Les éléments map () et filter () intégrés ont besoin d'objets de fonction, mais répertorie les compréhensions et les expressions de générateur . sont généralement plus lisibles que ces fonctions et peuvent couvrir tous les cas d'utilisation, sans avoir besoin de lambdas.

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

Si vous avez encore besoin de lambda non couvert, vous pouvez envisager d'écrire un def , simplement pour être plus lisible. Si la fonction est plus complexe que celle du module opérateur , un code def est probablement préférable.

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

Autres conseils

En termes pratiques, il existe 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 beaucoup Twisted et je fais 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 considéré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.

Dans cette interview, Guido van Rossum dit qu'il aimerait ne pas avoir été laissé pour compte. 'en Python:

  

& Q. Quelles sont les fonctionnalités de Python qui vous plaisent 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. Un exemple serait certaines des fonctionnalités de programmation fonctionnelles, telles que les fonctions lambda. 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, telle qu'une liste.

  En pratique, cela ne s'est pas très bien passé. Python n'a que deux champs d'application: local et global. Cela rend l'écriture des fonctions lambda pénible, car vous voulez souvent accéder aux variables de l'étendue dans laquelle lambda a été défini, mais vous ne pouvez pas le faire à cause 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 fonctionne 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):

Pour n = 1000, voici le temps d'appeler une fonction par rapport à 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

Je suis d'accord avec le conseil de nosklo: si vous devez attribuer 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 )

Performance:

La création d'une fonction avec lambda est un peu plus rapide que de la créer avec def . La différence est due à def la création d’une entrée de nom dans la table des paramètres locaux. 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 de lambda est plus courte et "plus facile". 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, alors ce que vous perdez en longueur et en complexité peut être récupéré dans la confusion par d'autres codeurs.

Limitations:

    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 à mariner.
  • Les noms des fonctions
  • def doivent être choisis avec soin pour être raisonnablement descriptifs et uniques, ou du moins, inutilisés dans la portée.

Cohérence:

Python évite généralement les conventions de programmation fonctionnelles au profit de la sémantique procédurale et de la sémantique objective. L’opérateur lambda contraste directement avec ce biais. De plus, comme alternative au def déjà répandu, la fonction lambda ajoute de la diversité à votre syntaxe. Certains considèrent que cela est moins cohérent.

Fonctions préexistantes:

Comme d'autres l'ont déjà noté, de nombreuses utilisations de lambda dans le champ peuvent être remplacées par des membres de opérateur 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.

Principe Pythonic: "Il devrait exister un" # 8212; et de préférence un seul & # 8212; moyen évident de le faire.

Cela est similaire à la doctrine source unique de vérité . Malheureusement, le principe de la seule façon évidente de le faire a toujours été davantage une aspiration mélancolique à 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 filtre :

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

lambda et def sont identiques.

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 ne casse manifestement rien, est "Pythonic". assez.

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 un def pour la deuxième dimension. Ceci est encore plus significatif pour les dimensions supérieures.

L’utilisation principale de lambda a toujours été pour les fonctions de rappel simples, et pour les fonctions mapper, réduire, filtrer, qui nécessitent une fonction en tant qu’argument. La compréhension des listes 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 concret d’utilisation quotidienne de lambda. Par conséquent, évitez lambda et créez des fonctions imbriquées.

Une limitation importante des lambdas est qu’ils ne peuvent pas contenir autre chose 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 étant 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.

Modifier: Il s'agit d'une question assez ancienne et mes opinions à ce sujet ont quelque peu changé.

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 (conseil, 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 un lambda ou def . De nombreuses autres utilisations courantes peuvent être satisfaites par une combinaison des modules opérateur , itertools et functools .

  

Plus préférable: fonctions lambda ou fonctions imbriquées ( def )?

Il existe 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 ont 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

Les lambdas étant des fonctions, ce sont des objets de première classe.

Les lambdas et les fonctions:

  • peut être passé comme argument (comme une fonction normale)
  • lorsqu’il est créé dans une fonction externe, devient une fermeture par rapport aux 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 complète.

Le __ nom __ 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, toto 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 sans problème - car il connaît son propre nom:

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

Les Lambdas n'ont ni annotations ni docstring

En gros, 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 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 la même information aux lambdas:

>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda (...)

Mais nous pouvons 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 gâche la sortie de l'aide, cependant.

Lambdas peut uniquement renvoyer une expression

Lambdas ne peut pas renvoyer d’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 , vous pouvez probablement accomplir la même chose avec un lambda, mais la complexité ajoutée nuit davantage à l'écriture de code clair.

Nous utilisons Python pour plus de clarté et de facilité de maintenance. La surutilisation de lambdas peut agir contre cela.

L'incident uniquement 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, comme Python est strictement évalué, il n’ya aucun gain de performances à le faire, mis à part le fait d’éviter la recherche de noms.

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 pense que la légère différence de temps ci-dessus peut être attribuée à la recherche de nom dans return_nullary_function - notez qu'il 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 non nom?

Pour toutes ces raisons, je préfère créer des fonctions avec def plutôt qu'avec lambda .

  • Temps de calcul.
  • Fonction sans nom.
  • Réaliser une fonction et de nombreuses utilisations.

Prenons 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

Si vous allez simplement affecter le lambda à une variable de la portée locale, vous pouvez également utiliser 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)

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

Puisque les lambdas peuvent être évalués 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 lambdas sont (c) plus minces.

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

E.G:

Vous avez une fonction avec cette signature: myFunction (données, fonction de rappel).

Vous souhaitez transmettre une fonction qui ajoute 2 éléments.

Utilisation de lambda:

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

La manière 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’affichage des éléments pour list et dict. Vraiment cool.

lambda est utile pour générer de nouvelles fonctions:

def somefunc(x): return lambda y: x+y
f = somefunc(10)
f(2)
>>> 12
f(4)
>>> 14

Une différence majeure est que vous ne pouvez pas utiliser les fonctions def en ligne, ce qui est à mon avis le cas d'utilisation le plus pratique pour une fonction lambda . Par exemple, lors du tri d'une liste d'objets:

my_list.sort(key=lambda o: o.x)

Par conséquent, je suggérerais de limiter l'utilisation de lambdas à ce type d'opérations triviales, qui ne bénéficient pas non plus réellement de la documentation automatique fournie par la désignation de la fonction.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top