More preferable: lambda functions or nested functions (def
)?
There is one advantage to using a lambda over a regular function: they are created in an expression.
There are several drawbacks:
- no name (just
'<lambda>'
)
- no docstrings
- no annotations
- no complex statements
They are also both the same type of object. For those reasons, I generally prefer to create functions with the def
keyword instead of with lambdas.
First point - they're the same type of object
A lambda results in the same type of object as a regular function
>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
...
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True
Since lambdas are functions, they're first-class objects.
Both lambdas and functions:
- can be passed around as an argument (same as a regular function)
- when created within an outer function become a closure over that outer functions' locals
But lambdas are, by default, missing some things that functions get via full function definition syntax.
A lamba's __name__
is '<lambda>'
Lambdas are anonymous functions, after all, so they don't know their own name.
>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'
Thus lambda's can't be looked up programmatically in their namespace.
This limits certain things. For example, foo
can be looked up with serialized code, while l
cannot:
>>> 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
We can lookup foo
just fine - because it knows its own name:
>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>
Lambdas have no annotations and no docstring
Basically, lambdas are not documented. Let's rewrite foo
to be better documented:
def foo() -> int:
"""a nullary function, returns 0 every time"""
return 0
Now, foo has documentation:
>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:
foo() -> int
a nullary function, returns 0 every time
Whereas, we don't have the same mechanism to give the same information to lambdas:
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda (...)
But we can hack them on:
>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:
<lambda> lambda ) -> in
nullary -> 0
But there's probably some error messing up the output of help, though.
Lambdas can only return an expression
Lambdas can't return complex statements, only expressions.
>>> lambda: if True: 0
File "<stdin>", line 1
lambda: if True: 0
^
SyntaxError: invalid syntax
Expressions can admittedly be rather complex, and if you try very hard you can probably accomplish the same with a lambda, but the added complexity is more of a detriment to writing clear code.
We use Python for clarity and maintainability. Overuse of lambdas can work against that.
The only upside for lambdas: can be created in a single expression
This is the only possible upside. Since you can create a lambda with an expression, you can create it inside of a function call.
Creating a function inside a function call avoids the (inexpensive) name lookup versus one created elsewhere.
However, since Python is strictly evaluated, there is no other performance gain to doing so aside from avoiding the name lookup.
For a very simple expression, I might choose a lambda.
I also tend to use lambdas when doing interactive Python, to avoid multiple lines when one will do. I use the following sort of code format when I want to pass in an argument to a constructor when calling 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
And now:
>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304
I believe the slight time difference above can be attributed to the name lookup in return_nullary_function
- note that it is very negligible.
Conclusion
Lambdas are good for informal situations where you want to minimize lines of code in favor of making a singular point.
Lambdas are bad for more formal situations where you need clarity for editors of code who will come later, especially in cases where they are non-trivial.
We know we are supposed to give our objects good names. How can we do so when the object has no name?
For all of these reasons, I generally prefer to create functions with def
instead of with lambda
.