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 def
palabra 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, foo
se puede buscar con código serializado, mientras l
que 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 foo
bien, 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 foo
para 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 def
lugar de con lambda
.
lambda
, pero no estoy de acuerdo en que esto es "muy raro", es común para las funciones clavesorted
oitertools.groupby
etc., por ejemplosorted(['a1', 'b0'], key= lambda x: int(x[1]))