Lambda Expressions
Aug 22, 2018
Last updated
Aug 22, 2018
Last updated
Pythonâs Lambda Expressions, using the lambda operator, are quite controversial. Some call them clunky, others call them elegant.
As a person who likes the functional programming paradigm, I like to sprinkle my code with lambda expressions from time to time, as long as itâs in small, contained doses. However I do see how putting a kinda long keyword in the middle of a line, and defining a function anonymously under the assumption that it will not be used in the rest of the program, may end up being a problem.
But without getting ahead of ourselves, let us start by defining the relevant terms here:
A lambda expression is an anonymous, inline declaration of a function, usually passed as an argument. It can do anything a regular function can, except it canât be called outside of the line where it was defined, since it is anonymous: it has no name.
The snippet above shows a comparison between using a function defined regularly (or imperatively, for those functional people in a corner who feel discriminated by my imperative-privileged talk), one defined by a lambda and assigned to a variable (which can be done, but is usually seen as a bad practice) and an inline call to a lambda function (I donât even need to tell you why this shouldnât be done too much).
Those three pieces of code end up returning the same value, but each of them has different effects in scope and memory: two define a callable object for the rest of the program, while the last one creates an anonymous function that wonât be called in any other line.
In case you are not fluent in List Comprehensions yet, hereâs a regular, imperative, for-loop example:
You will probably agree that map is less clumsy and takes less space than regular for-loops, but can be a bit less intuitive than List Comprehensions.
Weâve talked enough about the criticisms (harder to read code, a clumsy keyword), letâs talk about the practical side.
Let us introduce two more of Pythonâs native keywords: map and filter.
The map function, for those unfamiliar with functional programming, is a higher-order function â that is, a function that takes at least one function as an input, or produces one as its output.
In its formal definition, its parameters are a function, and a sequence (any iterable object), and its output is another sequence, containing the result of applying the given function to each element in the supplied sequence, in the same order.
Sound familiar? Thatâs because calling a map of a function over a list is almost equivalent to using a list comprehension! There are some caveats to that statement though, and I will address them promptly.
The following equivalence almost holds true:
Note that in the last two lines, I passed the triple and thrice functions as arguments, thanks to map being a higher order function.
If you were to iterate each of those five variables, they would all yield the same values. However in Python 3+, on printing them, youâd see some are lists, and others are map objects. Thatâs right, applying map on a list will return a Generator! That basically means it will generate a sequence thatâs lazily evaluated, can be iterated on and must be cast into a list in order to be sliced or indexed. On the other hand, map returns a normal list in Python 2.7.
So thatâs where lambdas and maps make a sort of synergy: As fluid as writing a line using map can be, it can become even more fluid if you can invent your small function on the fly. Once you get the hang of it, youâll start thinking in terms of mapping, and appreciate this feature. But beware, unreadable code can fester very quickly if maps are left unguarded.
For completion, I will also introduce you to filter. Calling filter in a sequence is the same as adding an if at the end of a List Comprehension, except it leaves a functional aftertaste. The following snippets are equivalent:
As with map, filter returns a Generator (in this case a filter object), but casting it to a list reveals it is equivalent to using an if in a List Comprehension.
I ran a few benchmarks comparing normal for-loops, List Comprehensions and map. I wasnât sure what the results would be, but have a few theories about them.
Here are a few things I defined before running the experiment:
And this is the code for the benchmarks I ran:
I compared a regular, imperative list declaration using append calls, one using List Comprehensions, and a last one calling map.
In Python 2.7, my results were the following:
6.00 seconds for list a
4.12 seconds for list b
3.53 seconds for list c (the one using map)
I actually donât know or have any theories on what optimization map uses in Python 2.7, but it came out faster than a List Comprehension! So thereâs a trade off to be made when choosing between the clarity of the latter and the speed of the former. It should be noted that I ran this experiment many times and had consistent results.
In Python 3, I had surprising results: the map test ran in less than a millisecond, while the list comprehension one took 5 seconds! Thatâs when I remembered to cast the result to a list to make the playground fair, as the Generator will of course load a lot faster, thanks to not having to initialize its values. These are the results:
7.08 seconds for list a
5.1 seconds for list b
5.1 seconds for list c
I had to run this one many times to check, but they basically take the same time. Itâs apparent theyâre both doing very similar things under the hood, and the choice between them then becomes only one of clarity, and whether laziness will be useful.
So to sum up, lambdas can be clunky, but they also add a lot of expression power to your code. Filter and map can be elegant to some, but donât add a lot to the table in terms of performance (and are seen as less Pythonic than List Comprehensions by some people).
My personal opinion is I like that Python has these features, because they make some lines of code more beautiful, but generally donât like them as much as List Comprehensions. Without tail call elimination and other optimizations, it can be a bit challenging to cite good reasons to use functional programming in Python (whereas we can actually gain a lot from using it in, say, Haskell) other than style and laziness (the good kind!).
What is your take? Is there some other functional topic youâd like me to discuss? Are you angry about me not mentioning reduce or fold? What other functional things would you like to see brought over to Python? Which ones do you dislike? I am sure you have an opinion on this, and I would love to learn more about it. Iâd also love to know if there are non-trivial cases where map is actually faster than List Comprehensions, and I am just not seeing them.
That was my introduction to the functional programming features in Python, and I hope you found it useful or at least had some fun reading it.
For more Python tutorials, tips and tricks, try visiting my Data Science website!
If you want to become a Data Scientist, check out my recommended Machine Learning books.
Reference : towardsdatascience.com