¿Qué es más preferible usar: funciones lambda o funciones anidadas ('def')?

101

Utilizo principalmente funciones lambda, pero a veces uso funciones anidadas que parecen proporcionar el mismo comportamiento.

Aquí hay algunos ejemplos triviales en los que funcionalmente hacen lo mismo si alguno de ellos se encontrara dentro de otra función:

Función lambda

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

Función anidada

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

>>> b(5)
6

¿Hay ventajas de usar uno sobre el otro? (¿Rendimiento? ¿Legibilidad? ¿Limitaciones? ¿Consistencia? Etc.)

¿Incluso importa? Si no es así, entonces viola el principio Pythonic:

Debe haber una, y preferiblemente solo una, forma obvia de hacerlo .

Rayo
fuente

Respuestas:

105

Si necesita asignar el lambdaa un nombre, use un defen su lugar. defLos s son simplemente azúcar sintáctico para una tarea, por lo que el resultado es el mismo y son mucho más flexibles y legibles.

lambdas se puede usar para usar una vez, deseche las funciones que no tendrán un nombre.

Sin embargo, este caso de uso es muy raro. Rara vez es necesario pasar objetos de función sin nombre.

Las funciones integradas map()y filter()necesitan objetos de función, pero enumeran comprensiones y expresiones generadoras son generalmente más legibles que esas funciones y pueden cubrir todos los casos de uso, sin la necesidad de lambdas.

Para los casos en que realmente necesita un objeto de función pequeño, debe usar las operatorfunciones del módulo, como en operator.addlugar delambda x, y: x + y

Si aún necesita algunos que lambdano están cubiertos, podría considerar escribir un def, solo para que sea más legible. Si la función es más compleja que las del operatormódulo, defprobablemente a sea mejor.

Por lo tanto, los lambdacasos de buen uso del mundo real son muy raros.

nosklo
fuente
9
Estoy de acuerdo con la respuesta sobre cuándo usarlo lambda, pero no estoy de acuerdo en que esto es "muy raro", es común para las funciones clave sortedo itertools.groupbyetc., por ejemplosorted(['a1', 'b0'], key= lambda x: int(x[1]))
Chris_Rands
30

Prácticamente hablando, para mí hay dos diferencias:

La primera es sobre lo que hacen y lo que devuelven:

  • def es una palabra clave que no devuelve nada y crea un 'nombre' en el espacio de nombres local.

  • lambda es una palabra clave que devuelve un objeto de función y no crea un 'nombre' en el espacio de nombres local.

Por lo tanto, si necesita llamar a una función que toma un objeto de función, la única forma de hacerlo en una línea de código Python es con una lambda. No hay equivalente con def.

En algunos marcos, esto es bastante común; por ejemplo, utilizo Twisted mucho, por lo que hago algo como

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

es bastante común y más conciso con lambdas.

La segunda diferencia tiene que ver con lo que puede hacer la función real.

  • Una función definida con 'def' puede contener cualquier código Python
  • Una función definida con 'lambda' tiene que evaluarse como una expresión y, por lo tanto, no puede contener declaraciones como print, import, raise, ...

Por ejemplo,

def p(x): print x

funciona como se esperaba, mientras

lambda x: print x

es un SyntaxError.

Por supuesto, hay soluciones alternativas: sustitúyalas printcon sys.stdout.writeo importcon __import__. Pero normalmente es mejor optar por una función en ese caso.

Thomas Vander Stichele
fuente
22

En esta entrevista, Guido van Rossum dice que desearía no haber dejado 'lambda' en Python:

" P. ¿Con qué característica de Python está menos satisfecho? En la práctica, no resultó tan bien. Python solo tiene dos ámbitos: local y global. Esto hace que escribir funciones lambda sea doloroso, porque a menudo desea acceder a variables en el alcance donde se definió el lambda, pero no puede debido a los dos alcances. Hay una forma de evitar esto, pero es algo complicado. A menudo parece mucho más fácil en Python usar un ciclo for en lugar de jugar con Las funciones lambda, map y friends funcionan bien solo cuando ya hay una función incorporada que hace lo que usted quiere.

A veces me apresuré a aceptar contribuciones y luego me di cuenta de que era un error. Un ejemplo serían algunas de las características de programación funcional, como las funciones lambda. Lambda es una palabra clave que le permite crear una pequeña función anónima; funciones integradas como mapa, filtro y reducción ejecutan una función sobre un tipo de secuencia, como una lista.

En mi humilde opinión, las Iambdas pueden ser convenientes a veces, pero generalmente son convenientes a expensas de la legibilidad. ¿Puedes decirme qué hace esto?

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

Lo escribí y me tomó un minuto entenderlo. Esto es del Proyecto Euler; no diré qué problema porque odio los spoilers, pero se ejecuta en 0.124 segundos :)

Chris Lawlor
fuente
20
Tenga en cuenta que la entrevista es bastante antigua, y Python hace mucho que agregó ámbitos anidados, lo que hace que el argumento que da contra lambda ya no sea relevante. Estoy seguro de que todavía lamenta lambda, pero no lo suficiente como para eliminarlo en Python 3.0.
Thomas Wouters
10
Realmente, su ejemplo debería ser un argumento en contra de las frases ingeniosas, no de las lambdas. Además, debería haber utilizado la función de suma incorporada en lugar de reducir con una lambda: str (sum (map (lambda x: x ** x, range (1001)))) [: - 10]
Triptych
2
@ThomasWouters: Entiendo que lambdano ser eliminado en 3.0 estuvo cerca, y que Guido no estaba luchando por mantenerlo.
Ethan Furman
11

Para n = 1000, aquí hay algo de tiempo para llamar a una función frente a una 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
Andy Hayden
fuente
3
Es interesante ver que las versiones lambda y definidas son aproximadamente equivalentes. La última prueba tomó más tiempo porque Python probablemente necesitaba asignar espacio cada vez que definía esa función lambda.
hlin117
Supongo que esto tiene sentido ya que la definición puede hacer referencia a variables locales (que pueden haber cambiado) ... aunque en el caso de que no lo haga, como aquí, cpython podría hacer un mejor trabajo.
Andy Hayden
Utilice dis.dis; Su (lambda x, y: x * y) crea la función en cada ciclo. Si crea el lambda antes del ciclo (también conocido como f = lambda x, y: x * y), el código de bytes para llamar a la función será exactamente el mismo que g / f en su ejemplo anterior, por lo tanto, el rendimiento de lambda es el mismo como una función def. Así que lambda o def como sin impacto si lo usas igual. Haga lo inverso, declare la función f () dentro del ciclo, luego llámelo ...
tito
@tito Creo que eso es precisamente lo que demuestran los 3 ejemplos cronometrados ...
Andy Hayden
@tito oh, estás diciendo definir la función en el ciclo, claro, pero yo diría que es un patrón inusual. No estoy seguro de por qué esto necesitaba el voto negativo en ese comentario ...
Andy Hayden
7

Actuación:

Crear una función con lambdaes un poco más rápido que crearla con def. La diferencia se debe a la defcreación de una entrada de nombre en la tabla de locales. La función resultante tiene la misma velocidad de ejecución.


Legibilidad:

Las funciones Lambda son algo menos legibles para la mayoría de los usuarios de Python, pero también mucho más concisas en algunas circunstancias. Considere la posibilidad de pasar de usar una rutina no funcional a una funcional:

# 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)

Como puede ver, la lambdaversión es más corta y "más fácil" en el sentido de que solo necesita agregar lambda v:a la versión original no funcional para convertir a la versión funcional. También es mucho más conciso. Pero recuerde, muchos usuarios de Python se sentirán confundidos por la sintaxis lambda, por lo que lo que pierde en longitud y complejidad real podría recuperarse en la confusión de otros programadores.


Limitaciones:

  • lambda Las funciones solo se pueden usar una vez, a menos que se asignen a un nombre de variable.
  • lambda Las funciones asignadas a los nombres de las variables no tienen ventaja sobre def funciones.
  • lambda Las funciones pueden ser difíciles o imposibles de eliminar.
  • def Los nombres de las funciones deben elegirse cuidadosamente para que sean razonablemente descriptivos y únicos o, al menos, no se utilicen en su alcance.

Consistencia:

Python evita principalmente las convenciones de programación funcional a favor de una semántica objetiva más simple y de procedimiento. El lambdaoperador contrasta directamente con este sesgo. Además, como alternativa a la ya prevalente def, la lambdafunción agrega diversidad a su sintaxis. Algunos lo considerarían menos consistente.


Funciones preexistentes:

Como han señalado otros, muchos usos de lambdaen el campo pueden ser reemplazados por miembros de los operatoru otros módulos. Por ejemplo:

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

El uso de la función preexistente puede hacer que el código sea más legible en muchos casos.


El principio pitónico: "Debe haber una, y preferiblemente solo una, forma obvia de hacerlo"

Eso es similar a la fuente única de la doctrina de la verdad . Desafortunadamente, el principio de una única forma obvia de hacerlo siempre ha sido más una aspiración nostálgica para Python que un verdadero principio rector. Considere las muy poderosas comprensiones de matrices en Python. Son funcionalmente equivalentes a las funciones mapy filter:

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

lambday defson iguales.

Es una cuestión de opinión, pero yo diría que cualquier cosa en el lenguaje Python destinada al uso general que obviamente no rompa nada es suficientemente "Pythonic".

Pi Marillion
fuente
7

Más preferible: ¿funciones lambda o funciones anidadas ( def)?

Hay una ventaja de usar una lambda sobre una función regular: se crean en una expresión.

Hay varios inconvenientes:

  • sin nombre (solo '<lambda>')
  • sin cadenas de documentos
  • sin anotaciones
  • sin declaraciones complejas

También son ambos del mismo tipo de objeto. Por esas razones, generalmente prefiero crear funciones con la defpalabra clave en lugar de con lambdas.

Primer punto: son el mismo tipo de objeto

Una lambda da como resultado el mismo tipo de objeto que una función regular

>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
... 
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True

Dado que las lambdas son funciones, son objetos de primera clase.

Ambas lambdas y funciones:

  • se puede pasar como un argumento (igual que una función regular)
  • cuando se crea dentro de una función externa se convierte en un cierre sobre los locales de las funciones externas

Pero a las lambdas, por defecto, les faltan algunas cosas que las funciones obtienen a través de la sintaxis de definición de función completa.

Un lamba __name__es'<lambda>'

Después de todo, las lambdas son funciones anónimas, por lo que no conocen su propio nombre.

>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'

Por lo tanto, los lambda no se pueden buscar programáticamente en su espacio de nombres.

Esto limita ciertas cosas. Por ejemplo, foose puede buscar con código serializado, mientras lque no se puede:

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

Podemos buscar foobien, porque conoce su propio nombre:

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

Las lambdas no tienen anotaciones ni cadenas de documentos

Básicamente, las lambdas no están documentadas. Reescribamos foopara estar mejor documentados:

def foo() -> int:
    """a nullary function, returns 0 every time"""
    return 0

Ahora, foo tiene documentación:

>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:

foo() -> int
    a nullary function, returns 0 every time

Considerando que, no tenemos el mismo mecanismo para dar la misma información a las lambdas:

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

<lambda> lambda (...)

Pero podemos piratearlos en:

>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda ) -> in
    nullary -> 0

Sin embargo, probablemente haya algún error que arruine la salida de la ayuda.

Lambdas solo puede devolver una expresión

Las lambdas no pueden devolver declaraciones complejas, solo expresiones.

>>> lambda: if True: 0
  File "<stdin>", line 1
    lambda: if True: 0
             ^
SyntaxError: invalid syntax

Es cierto que las expresiones pueden ser bastante complejas, y si se esfuerza mucho , probablemente pueda lograr lo mismo con una lambda, pero la complejidad adicional es más un detrimento para escribir código claro.

Usamos Python para mayor claridad y facilidad de mantenimiento. El uso excesivo de lambdas puede ir en contra de eso.

La única ventaja de las lambdas: se puede crear en una sola expresión

Esta es la única ventaja posible. Dado que puede crear una lambda con una expresión, puede crearla dentro de una llamada de función.

La creación de una función dentro de una llamada de función evita la búsqueda de nombre (económica) frente a una creada en otro lugar.

Sin embargo, dado que Python se evalúa estrictamente, no hay otra ganancia de rendimiento al hacerlo además de evitar la búsqueda de nombres.

Para una expresión muy simple, podría elegir una lambda.

También tiendo a usar lambdas cuando hago Python interactivo, para evitar múltiples líneas cuando una sirve. Utilizo el siguiente tipo de formato de código cuando quiero pasar un argumento a un constructor al llamar 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

Y ahora:

>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304

Creo que la ligera diferencia de tiempo anterior se puede atribuir a la búsqueda de nombre en return_nullary_function; tenga en cuenta que es muy insignificante.

Conclusión

Las lambdas son buenas para situaciones informales en las que desea minimizar las líneas de código a favor de hacer un punto singular.

Las lambdas son malas para situaciones más formales en las que se necesita claridad para los editores de código que vendrán más tarde, especialmente en los casos en que no son triviales.

Sabemos que se supone que debemos dar buenos nombres a nuestros objetos. ¿Cómo podemos hacerlo cuando el objeto no tiene nombre?

Por todas estas razones, generalmente prefiero crear funciones con en deflugar de con lambda.

Aaron Hall
fuente
6

Estoy de acuerdo con el consejo de nosklo: si necesitas darle un nombre a la función, usa def. Reservo lambdafunciones para los casos en los que solo estoy pasando un breve fragmento de código a otra función, por ejemplo:

a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )
Dan Lenski
fuente
3
En la mayoría de las combinaciones de mapa / lambda, puede reemplazarlo con una lista de comprensión o una función más apropiada. Por ejemplo, "mapa (suma, a)" o "[x [0] + x [1] para x en a]"
John Millikin
Sí, eso es verdad. Sin embargo, a veces prefiero map (). En su mayoría, esto fue solo un ejemplo artificial del uso de una función en línea.
Dan Lenski
exactamente ... La mayoría de los ejemplos son artificiales, porque su uso no es natural y existen mejores formas prácticas en la mayoría de los casos.
nosklo
5

Si bien está de acuerdo con las otras respuestas, a veces es más legible. He aquí un ejemplo en el que lambdaviene muy bien, en un caso de uso sigo encontrando de una N dimensiones defaultdict.
He aquí un ejemplo:

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

Lo encuentro más legible que crear un defpara la segunda dimensión. Esto es aún más significativo para dimensiones superiores.

Jonathan
fuente
from functools import partial; defaultdict(partial(defaultdict, list)). Asigne el parcial a un nombre si desea usarlo más de una vez. Pero, si sigues encontrando esta construcción, significa que no estás SECO. Factorizarlo en una biblioteca de utilidades. Puede usar esta construcción para crear un defaultdict arbitrario n-dimensional usando otras funciones (o un bucle o recursividad).
DylanYoung
3

El uso principal de lambda siempre ha sido para funciones de devolución de llamada simples y para map, reduce, filter, que requieren una función como argumento. Con listas por comprensión que se convierten en la norma, y ​​el agregado permitido si como en:

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

Es difícil imaginar un caso real para el uso de lambda en el uso diario. Como resultado, diría, evite lambda y cree funciones anidadas.

apg
fuente
3

Una limitación importante de las lambdas es que no pueden contener nada más que una expresión. Es casi imposible que una expresión lambda produzca algo más que efectos secundarios triviales, ya que no puede tener un cuerpo tan rico como una deffunción ed.

Dicho esto, Lua influyó en mi estilo de programación hacia el uso extensivo de funciones anónimas, y ensucio mi código con ellas. Además de eso, tiendo a pensar en map / reduce como operadores abstractos de maneras que no considero listas por comprensión o generadores, casi como si estuviera aplazando una decisión de implementación explícitamente usando esos operadores.

Editar: Esta es una pregunta bastante antigua, y mis opiniones sobre el tema han cambiado, un poco.

En primer lugar, estoy fuertemente predispuesto a no asignar una lambdaexpresión a una variable; ya que python tiene una sintaxis especial solo para eso (pista, def). Además de eso, muchos de los usos de lambda, incluso cuando no reciben un nombre, tienen implementaciones predefinidas (y más eficientes). Por ejemplo, el ejemplo en cuestión se puede abreviar como just (1).__add__, sin la necesidad de envolverlo en lambdao def. Muchos otros usos comunes pueden satisfacerse con alguna combinación de los módulos operator, itertoolsy functools.

SingleNegationElimination
fuente
1
(1).__add__- La llamada directa a métodos dunder casi nunca debería suceder. Mil lambdas por cada llamada directa al dunder.
Ethan Furman
1
@EthanFurman: Bueno, en mi experiencia, las llamadas de la naturaleza (1).__add__son algo poco comunes, pero no me acercaría a "debería". sin lugar a dudas, creo que el primero es mucho más legible lambda x: 1 + x. Si tuviéramos algo más parecido a la notación de corte de Haskells, (1+)sería genial, pero tenemos que conformarnos con lo que semánticamente es exactamente eso, el nombre del método dunder.
SingleNegationElimination
2
  • Tiempo de computación.
  • Función sin nombre.
  • Para lograr una función y muchas funciones de uso.

Considerando un simple ejemplo,

# 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
bhargav patel
fuente
1

Si solo va a asignar la lambda a una variable en el ámbito local, también puede usar def porque es más legible y se puede expandir más fácilmente en el futuro:

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

o

def fun(a, b): return a ** b # more readable
map(fun, someList)
demasiado php
fuente
Ambos from operator import pow;map(pow, someList)y (a**b for a,b in someList)son aún más legibles.
InQβ
1

Un uso para lambdas que he encontrado ... es en mensajes de depuración.

Dado que las lambdas se pueden evaluar de forma perezosa, puede tener un código como este:

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

en lugar de posiblemente caro:

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

que procesa la cadena de formato incluso si la llamada de depuración no produce salida debido al nivel de registro actual.

Por supuesto, para que funcione como se describe, el módulo de registro en uso debe admitir lambdas como "parámetros perezosos" (como lo hace mi módulo de registro).

La misma idea puede aplicarse a cualquier otro caso de evaluación perezosa para la creación de valor de contenido bajo demanda.

Por ejemplo, este operador ternario personalizado:

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)

en vez de:

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

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

con lambdas solo se evaluará la expresión seleccionada por la condición, sin lambdas se evaluarán ambas.

Por supuesto, podría simplemente usar funciones en lugar de lambdas, pero para expresiones cortas, las lambdas son (c) más delgadas.

Glushiator
fuente
1
NB loggingya tiene un formato diferido: log.debug("this is my message: %r", some_data)solo formateará cuando / si se solicita el mensaje.
j08lue
El método @ j08lue lambda omite la evaluación de todo en caso de que no se produzca la salida de depuración, en el caso de que muestre que some_datapodría ser una expresión costosa o una llamada a una función / método.
Glushiator
0

Estoy de acuerdo con nosklo. Por cierto, incluso con un uso único, tírelo único , la mayoría de las veces solo desea usar algo del módulo del operador.

P.EJ :

Tiene una función con esta firma: myFunction (datos, función de devolución de llamada).

Quieres pasar una función que agregue 2 elementos.

Usando lambda:

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

La forma pitónica:

import operator
myFunction(data, operator.add)

Por supuesto, este es un ejemplo simple, pero hay muchas cosas que proporciona el módulo de operador, incluidos los establecedores / captadores de elementos para lista y dic. Realmente genial.

e-satis
fuente
-1

Una diferencia importante es que no puede usar deffunciones en línea, que es en mi opinión el caso de uso más conveniente para una lambdafunción. Por ejemplo, al ordenar una lista de objetos:

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

Por lo tanto, sugeriría mantener el uso de lambdas para este tipo de operaciones triviales, que tampoco se benefician realmente de la documentación automática proporcionada al nombrar la función.

Ali Rasim Kocal
fuente
-2

lambda es útil para generar nuevas funciones:

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