¿Cuál es más preferible utilizar en Python: funciones lambda o funciones anidadas ('def')?

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

Pregunta

Principalmente uso 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 se encontraran 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 al usar uno sobre el otro? (¿Rendimiento? ¿Legibilidad? ¿Limitaciones? ¿Consistencia? Etc.)

¿Incluso importa? Si no lo hace, entonces eso viola el principio de Pythonic:

  

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

¿Fue útil?

Solución

Si necesita asignar el lambda a un nombre, use un def en su lugar. Los def son solo azúcar sintáctica para una tarea, por lo que el resultado es el mismo, y son mucho más flexibles y legibles.

lambda s se pueden usar para usar una vez, tirar las funciones que no tienen nombre.

Sin embargo, este caso de uso es muy raro. Rara vez necesita pasar objetos de funciones sin nombre.

El map () y filter () incorporado necesita objetos de función, pero enumera las comprensiones y las expresiones del generador 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ña, debe usar las funciones del módulo operador , como operator.add en lugar de lambda x, y: x + y

Si aún necesita algo de lambda no cubierto, podría considerar escribir una def , para que sea más legible. Si la función es más compleja que las del módulo operador , es probable que una def sea mejor.

Por lo tanto, los casos de uso lambda del mundo real son muy raros.

Otros consejos

En términos prácticos, 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 de Python es con un lambda. No hay equivalente con la definición.

En algunos marcos esto es bastante común; por ejemplo, uso mucho Twisted , y por eso hago algo como

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

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

La segunda diferencia es sobre lo que se permite que haga la función real.

  • Una función definida con 'def' puede contener cualquier código de Python
  • Una función definida con 'lambda' tiene que evaluar una expresión y, por lo tanto, no puede contener declaraciones como imprimir, importar, aumentar, ...

Por ejemplo,

def p(x): print x

funciona como se esperaba, mientras que

lambda x: print x

es un error de sintaxis.

Por supuesto, hay soluciones: sustituya print con sys.stdout.write , o import con __import __ . Pero normalmente es mejor ir con una función en ese caso.

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

  

" Q. ¿Con qué función de Python estás menos satisfecho?

  A veces he sido demasiado rápido en aceptar contribuciones y luego me di cuenta de que era un error. Un ejemplo sería algunas de las funciones 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 mapear, filtrar y reducir ejecutar una función sobre un tipo de secuencia, como una lista.

  En la práctica, no resultó tan bien. Python solo tiene dos ámbitos: local y global. Esto hace que la escritura de las funciones lambda sea dolorosa, porque a menudo desea acceder a las variables en el ámbito donde se definió la lambda, pero no puede debido a los dos ámbitos. Hay una forma de evitar esto, pero es algo así como una confusión. A menudo, parece mucho más fácil en Python usar un bucle for en lugar de jugar con las funciones lambda. el mapa y los amigos funcionan bien solo cuando ya hay una función incorporada que hace lo que usted quiere.

En mi humilde opinión, Iambdas puede ser conveniente a veces, pero generalmente es conveniente 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é cuál es el problema porque odio los spoilers, pero se ejecuta en 0.124 segundos :)

Para n = 1000, aquí hay un tiempo para llamar a una función frente a 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

Estoy de acuerdo con el consejo de nosklo: si necesita asignar un nombre a la función, use def . Reservo las funciones lambda para los casos en los que simplemente le paso 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 )

Rendimiento :

Crear una función con lambda es un poco más rápido que crearlo con def . La diferencia se debe a que def crea una entrada de nombre en la tabla locals. 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 son mucho más concisas en algunas circunstancias. Considere la posibilidad de convertir el uso de 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 versión de lambda es más corta y " más fácil " en el sentido de que solo necesita agregar lambda v: a la versión no funcional original para convertirla a la versión funcional. También es mucho más conciso. Pero recuerde, la sintaxis lambda confundirá a muchos usuarios de Python, de modo que lo que pierda en longitud y complejidad real se recuperará en la confusión de otros programadores.


Limitaciones:

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

Consistencia:

Python evita en su mayoría las convenciones de programación funcional en favor de la semántica objetiva de procedimiento y más simple. El operador lambda está en contraste directo con este sesgo. Además, como una alternativa a la def ya prevalente, la función lambda agrega diversidad a su sintaxis. Algunos lo considerarían menos consistente.


Funciones preexistentes:

Como han señalado otros, muchos de los usos del lambda en el campo pueden ser reemplazados por miembros del operador u 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 de Pythonic: & # 8220; Debe haber una & # 8212; y preferiblemente solo una & # 8212; forma obvia de hacerlo & # 8221;

Eso es similar a la fuente única de verdad . Desafortunadamente, el principio de una sola manera obvia de hacerlo siempre ha sido más una aspiración melancólica para Python, en lugar de un verdadero principio guía. Considera las muy poderosas comprensiones de matriz en Python. Son funcionalmente equivalentes a las funciones map y filter :

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

lambda y def son los mismos.

Es una cuestión de opinión, pero diría que cualquier cosa en el lenguaje Python para uso general que obviamente no rompe nada es " Pythonic " suficiente.

Aunque está de acuerdo con las otras respuestas, a veces es más legible. Aquí hay un ejemplo en el que lambda es útil, en un caso de uso que me encuentro con una N dimensional defaultdict .
Aquí hay un ejemplo:

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

Lo encuentro más legible que crear una def para la segunda dimensión. Esto es aún más significativo para las dimensiones más altas.

El uso principal de lambda siempre ha sido para funciones simples de devolución de llamada, y para mapear, reducir, filter, que requieren una función como argumento. Con la comprensión de las listas se está convirtiendo 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, evitar lambda y crear funciones anidadas.

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 la función ed def '.

Dicho esto, Lua influyó en mi estilo de programación hacia el uso extensivo de las funciones anónimas, y yo ensucio mi código con ellas. Además de eso, tiendo a pensar en mapear / reducir como operadores abstractos de una manera que no considero comprensiones de lista o generadores, casi como si estuviera aplazando una decisión de implementación explícitamente al usar esos operadores.

Editar: Esta es una pregunta bastante antigua, y mis opiniones al respecto han cambiado un poco.

En primer lugar, estoy fuertemente predispuesto a asignar una expresión lambda a una variable; Python tiene una sintaxis especial solo para eso (sugerencia, 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 a solo (1) .__ add__ , sin la necesidad de envolverlo en un lambda o def . Muchos otros usos comunes pueden satisfacerse con alguna combinación de los módulos operador , itertools y functools .

  

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

Hay una ventaja de usar un lambda sobre una función regular (se crean en una expresión), y varios inconvenientes. Por esa razón, prefiero crear funciones con la palabra clave def en lugar de con lambdas.

Primer punto: son el mismo tipo de objeto

Un lambda da como resultado el mismo tipo de objeto que una función normal

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

Tanto lambdas como funciones:

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

Pero, de forma predeterminada, a los lambdas faltan algunas cosas que las funciones obtienen a través de la sintaxis de definición de función completa.

El __name__ de lamba es '<lambda>'

Las Lambdas son funciones anónimas, después de todo, por lo que no saben su propio nombre.

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

Por lo tanto, no se puede buscar lambda mediante programación en su espacio de nombres.

Esto limita ciertas cosas. Por ejemplo, foo puede buscarse con un código serializado, mientras que l no 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 foo bien, porque conoce su propio nombre:

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

Las Lambdas no tienen anotaciones ni documentación.

Básicamente, las lambdas no están documentadas. Reescribamos foo para documentarlo mejor:

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:

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

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

Sin embargo, es probable que exista algún error que estropee la salida de la ayuda.

Lambdas solo puede devolver una expresión

Lambdas no puede 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 lo hace mucho es probable que puedas lograr lo mismo con un lambda, pero la complejidad agregada es más en detrimento de escribir un código claro.

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

El lado positivo only para las lambdas: se puede crear en una sola expresión

Este es el único alza posible. Ya que puede crear un lambda con una expresión, puede crearlo 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 nombres (barata) en comparación con una creada en otro lugar.

Sin embargo, dado que Python está estrictamente evaluado, no hay otra mejora de rendimiento al hacerlo además de evitar la búsqueda de nombres.

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

También tiendo a usar lambdas cuando hago Python interactivo, para evitar múltiples líneas cuando una lo hará. Utilizo el siguiente tipo de formato de código cuando quiero pasar un argumento a un constructor cuando llamo a 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 pequeña diferencia de tiempo anterior se puede atribuir a la búsqueda de nombre en return_nullary_function . Tenga en cuenta que es muy despreciable.

Conclusión

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

Lambdas es malo para situaciones más formales en las que necesita claridad para los editores de código que vendrán más adelante, especialmente en los casos en que no son triviales.

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

Por todos estos motivos, prefiero crear funciones con def en lugar de con lambda .

  • Tiempo de cálculo.
  • Función sin nombre.
  • Para lograr una función y muchas funciones de uso.

Considerando un ejemplo 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 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)

Un uso para las lambdas que he encontrado ... está en los mensajes de depuración.

Dado que las lambdas pueden evaluarse perezosamente, 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 resultados 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 se puede aplicar a cualquier otro caso de evaluación perezosa para la creación de valor de contenido a pedido.

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 lugar 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, simplemente puedes usar funciones en lugar de lambdas, pero para expresiones cortas las lambdas son (c) más magras.

Estoy de acuerdo con nosklo. Por cierto, incluso con la función usar una vez, desechar , la mayoría de las veces solo desea usar algo del módulo del operador.

E.G:

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

Desea pasar una función que agregue 2 elementos.

Usando lambda:

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

El camino pitónico:

import operator
myFunction(data, operator.add)

O claro, este es un ejemplo simple, pero hay muchas cosas que proporciona el módulo del operador, incluidos los elementos que configuran / obtienen para la lista y el dictado. Realmente genial.

lambda es útil para generar nuevas funciones:

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

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

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

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top