PYTHON PROGRAMMING
Many authors claim that in order to shorten your code, you can use lambda
functions to define functions and assign them to a name. Some beginners may feel like trying this approach; to be honest, not only did I try it at some point, but also I enjoyed it too much.
Note that this is a different situation from using an unnamed lambda
function as an argument to a higher-order function, like here:
>>> x = [
... ("Johny Faint", 197),
... ("Jon Krohn", 187),
... ("Anna Vanna", 178),
... ("Michael Ginger", 165),
... ("Viola Farola", 189)
... ]
>>> sorted(x, key=lambda x: -x[1])
[('Johny Faint', 197), ('Viola Farola', 189), ('Jon Krohn', 187), ('Anna Vanna', 178), ('Michael Ginger', 165)]
We will talk about assigning a lambda
function to a name, like here:
>>> sort_fun = lambda x: -x[1]
The difference lies in the assignment, represented by =
, which here means that we define function sort_fun(x)
, which will formally be a function with one argument, x
. The function assumes x
is an iterable with at least two elements. Of course, we know that this very function was constructed to be used for the same sorting as the one used above, like here:
>>> sorted(x, key=sort_fun)
[('Johny Faint', 197), ('Viola Farola', 189), ('Jon Krohn', 187), ('Anna Vanna', 178), ('Michael Ginger', 165)]
These two approaches differ. When we define a lambda
function inside a higher-order function, it cannot be used elsewhere; and it is strictly dedicated to this particular use. When we define a lambda
function and assign it to a name (sort_fun
), it can be used elsewhere in the code. Also, in Python, functions are functions irrespective of how they were defined, whether using def
or lambda
; hence, sort_fun()
is a valid Python function.
This article deals with named lambda
functions; that is, lambda
functions that are defined and assigned to a name. We will discuss whether you should define functions that way, or rather you should avoid doing that. We will see what PEP8 says about this, but also we will perform our own comparison of def
-defined and lambda
-defined functions. This will help us decide whether lambda
indeed offers an alternative way of defining simple functions in Python.
What does PEP8 say about this?
Although over 20-year old, we still use PEP8 as the "Style Guide for Python Code." So, if you need to check something related to coding style, PEP8 should be your first source.
The idea of using assigned lambda
functions goes against PEP8’s recommendation, which uses precise wording to state that you should not do this:
Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier…
So, does this really mean that using a named lambda
function is indeed against PEP8? I see two possibilities:
- No, it does not! This
lambda
-based PEP8 recommendation is outdated. For example, many developers disagree with the maximum line length of 79 characters that PEP8 recommends, considering it the breath of the old. Along the same lines, thelambda
suggestion is outdated, and we should not worry about going against PEP8. Wasn’t PEP8 created over twenty years ago, back in 2001? Should we indeed follow so old a style guide in something as modern as the Python programming language? - Yes, it does! The authors who suggest to break this
lambda
-based recommendation rely on only one premise: the number of characters. This would mean that if something is shorter, it’s better. The world is not that simple, and programming is not different: the brevity oflambda
functions is an insufficient factor alone. Listen to PEP8 and don’t use namedlambda
functions!
To be honest, some time ago I myself occasionally used such named lambda
definitions. I simply liked the way they looked: concise and different. Now I think it was this difference what attracted me in this approach. I know I tend to like complex solutions, ones that look different and atypical, as they make me feel I am a good programmer. But this is so misleading! Writing complex code does not mean being a good programmer. Now that I know this, I try to be careful and double-check whether or not I am overdoing with unnecessary complexity.
So, let’s be objective and analyze whether named lambda
-based function definitions can be better – and if so, when – than the def
-based function definitions.
Be aware that we do not discuss using lambda
s in general, but only named lambda
definitions, which assign a lambda
Function to a name, like here:
foo = lambda x, u: f"{x} and {u}"
Whatever we will come up with has nothing to do with using lambdas in higher-order functions.
Defining functions using lambda and def
Let’s make this comparison practical. To this end, I will use four functions:
- a function without arguments
- a function with one argument
- a function with two arguments
- a function with three arguments
For the last one, we will use a default value of one of the arguments. Here are the functions, defined in a traditional way:
def weather():
return "beautiful weather"
def square(x):
return x**2
def multiply(x, m):
"""Multiply x by m.
>>> multiply(10, 5)
50
>>> multiply("a", 3)
'aaa'
"""
return x * m
def join_sep(iter1, iter2, sep="-"):
return [f"{v1}{sep}{v2}" for v1, v2 in zip(iter1, iter2)]
Let’s see what these functions look like when defined using lambda
s:
weather = lambda: "beautiful weather"
square = lambda x: x**2
multiply = lambda x, m: x * m
join_sep =lambda iter1, iter2, sep="-": [f"{v1}{sep}{v2}" for v1, v2 in zip(iter1, iter2)]
We will compare the two approaches in terms of several aspects.
Code brevity and visual clutter
The lambda
code definitely looks more dense. Is it a good thing? Notice that assigned lambda
functions are not much shorter: they replace the following characters: def
, ():
, and return
with = lambda
. So, instead of 12
characters, they use 7
, so only 5
characters fewe r(ignoring white spaces). That’s not a lot.
I think the feeling of lambda
definitions’ greater density comes from presenting them in one line. Nevertheless, we can do the same using def
:
def square(x): return x**2
This definition and the lambda
one do not differ much in terms of length, do they? However, using the def
definition for such short functions, we use a new line after the first line for a reason – for me, it’s not more characters; instead, it helps avoid the visual clutter.
So, in terms of code brevity, I do not really see the point in choosing lambda
definitions over def
definitions.
In terms of visual clutter, lambda
definitions are in my eyes worse, meaning more cluttered. Compare, again, the multiply()
function defined using lambda
:
multiply = lambda x, m: x * m
and def
:
def multiply(x, m):
return x * m
The additional white space in the latter helps one to visually group the function into two elements:
- the function’s signature:
def multiply(x, m):
- the function’s body:
return x * m
In lambda
definitions, there is no such visual differentiation. Instead, we get visual clutter and density, which call for additional mental work needed to distinguish the function’s signature and body. What’s worse, we do not directly see the signature! We must create it in our minds.
There’s one more visual aspect of lambda
definitions that I don’t like, which also makes them visually cluttered. Namely, the function’s name and its arguments do not go together but are separated with "= lambda
".
Clarity, no annotations (type hints)
So, despite of what some authors say, lambda
definition’s brevity comes with visual clutter and increased code density. Hence, for me this brevity is a disadvantage rather than an advantage.
Maybe the lambda
functions’ code is clearer? In the code block below, is the latter definition clearer than the former?
def square(x):
return x**2
square = lambda x: x**2
While I do not have problems with reading and understanding this lambda
function, I wouldn’t say it’s clearer than the one defined using the def
keyword. To be honest, since I am accustomed to def
function definitions, the former is even clearer to me.
Our above discussion related to visual clutter and code density applies here, too. I like the two-line def
definition, which immediately shows the function’s signature and body. The lambda
definition, on the opposite, requires me to read through the whole code in one line, in order to see the function’s signature and body. This decreases code readability – even for this simple function.
Let’s see what it looks like in a function with two arguments:
def multiply(x, m):
return x * m
multiply = lambda x, m: x * m
This time, the def
version seems significantly clearer to me. This is, again, due to some problems with visually distinguishing the function’s signature (including function arguments) and body.
It’s not without reason that whoever suggests lambda
definitions, does that for short and simple functions. We can see this in the definitions of the join_sep
function:
def join_sep(iter1, iter2, sep="-"):
return [f"{v1}{sep}{v2}" for v1, v2 in zip(iter1, iter2)]
join_sep =lambda iter1, iter2, sep="-": [f"{v1}{sep}{v2}" for v1, v2 in zip(iter1, iter2)]
Is there anyone to choose join_sep()
defined using the lambda
keyword as the better one? For me, only the former can be considered readable while the latter is overly difficult – even though I do understand this lambda
definition. In this case, understanding does not mean appreciation.
I provided this example only to reinforce the point that even if you do decide to use lambda
definitions, you should do it for simple and short functions; join_sep()
is not simple enough.
The def
definitions, however, enable us to do one more thing in order to increase its clarity: docstrings. Let me repeat the example of multiply()
‘s definition:
def multiply(x, m):
"""Multiply x by m.
>>> multiply(10, 5)
50
>>> multiply("a", 3)
'aaa'
"""
return x * m
Docstrings are a powerful way of documenting functions and enriching them with unit tests (i.e., doctest
s). You add any type of documentation to named lambda
functions, except for inline comments. On the other hand, I suppose you would not use lambda
for a function that requires a docstring. I would, however, say that multiply()
does not require a docstring – but it is clearer with it.
Clarity, with annotations (type hints)
We know that when used wisely, type hints can increase function clarity. Consider these two examples:
from numbers import Number
def square(x: Number) -> Number:
return x**2
square = lambda x: x**2
def multiply(x: Number, m: Number) -> Number:
return x * m
multiply = lambda x, m: x * m
We cannot use function annotations in lambda
definitions. So, whenever you have a function whose signature uses helpful type hints, a def
-based definition will always be better, as it will convey more information about the function than its lambda
definition.
Now, a short mental exercise: imagine multiply()
with both docstring and type hints, and see how much better this definition would be from the bare named lambda
definition.
The above examples suggest that functions with two arguments (or more, for that matter) may lack type hints even more than single-argument functions.
Conclusion
We compared lambda
-based and def
-based function definitions in Python. In doing so, we used only two aspects: brevity and visual clutter; and clarity. The comparison was not very rich – because we did not need to make it rich. From whichever angle we looked at the two types of definition, def
always proved better than named lambda
.
Even in the case of the simplest functions, I did not find sufficient arguments for defining them using lambda
. More often than not, the corresponding def
definition was clearer, and even if a little longer, its code was less visually cluttered and dense.
It’s difficult for me to imagine a two-argument function, especially with a default value of at least one of the arguments, that would be clearer when lambda
defined than when def
defined.
Finally, def
definitions enables the developer to use three powerful tools that are not available in lambda
functions:
- function annotation (type hints)
- docstrings
- unit tests, via doctests inside the doctest
Thanks to them, we can make def
functions much more informative.
Thanks for reading this article. I hope you enjoyed it.
And I do hope I have convinced you. Even if both types of function definition seem equally fine to you— even then I would not use named lambda
definitions. This is because using them, you do not gain anything, at the same time risking that others will disagree with you. And if even that does not convince you, remember that doing so you’re going against PEP8.
So, why would you want to use named lambda
definitions when assigning a function to a name?
Resources
PEP 8 – Style Guide for Python Code
Python’s Type Hinting: Friend, Foe, or Just a Headache?
Lambda Functions with Practical Examples in Python
doctest – Test interactive Python examples – Python 3.11.0 documentation