Why your lambda function does not work
Lambda function in Python is designed to be a one-liner and throwaway function even without the needs to assign a function name, so it is also known as anonymous function. Comparing to the normal Python functions, you do not need to write the def and return keywords for lambda function, and it can be defined just at the place where you need it, so it makes your code more concise and looks a bit special. In this article, we will be discussing some unexpected results you may have encountered when you are using lambda function.
Basis usage of lambda
Let’s cover some basis of lambda function before we dive into the problems we are going solve in this article.
Below is the syntax to define lambda function:
lambda [arguments] : expression
As you can see lambda function can be defined with or without arguments, and take note that it only accepts one line of expression, not any of the Python statements. Expressions can be also statements, the difference is that you are able to evaluate a expression into values (or objects), e.g.: 2**2, but you may not be able to evaluate a statement like while(True): into a value. You can think there is an implicit “return” keyword before the expression, so your expression must be eventually computed into a value.
And here are some basic usage of lambda function:
square = lambda x: x**2 print(square(4)) #Output: 16 cryptocurrencies = [('Bitcoin', 10948.52),('Ethereum', 381.41),('Tether', 1.00), ('XRP', 0.249940), ('Bitcoin Cash', 231.86), ('Polkadot', 4.91), ('Binance Coin', 27.02), ('Chainlink', 10.47), ('Litecoin', 48.20), ('EOS', 2.69), ('TRON', 0.027157), ('Neo', 24.29), ('Stellar', 0.077903), ('Huobi Token', 4.91)] top5_by_name = sorted(cryptocurrencies, key=lambda token: token.lower())[0:5] print(top5_by_name) #Output: [('Binance Coin', 27.02), ('Bitcoin', 10948.52), ('Bitcoin Cash', 231.86), ('Chainlink', 10.47), ('EOS', 2.69)] lowest = min(cryptocurrencies, key=lambda token: token) print(lowest) #Output: ('TRON', 0.027157) highest = max(cryptocurrencies, key=lambda token: token) print(highest) #Output: ('Bitcoin', 10948.52) highest_in_local_currency = lambda exchange_rate: highest * exchange_rate highest_sgd = highest_in_local_currency(1.38) print(highest_sgd) #Output: 15108.9576
You can see that it is quite convenient when you just need a very short function to be supplied to another function which accepts argument like key=keyfunc, such as sorted, list.sort, min, max, heapq.nlargest, heapq.nsmallest, itertool.groupby and so on. The common thing about these use cases is that you do not need very complicated logic (can be written in one line) in the keyfunc and probably you will not reuse it in anywhere else. So it is the ideal scenario to use a lambda function.
Now Let’s expand further on our previous example, assuming the bitcoin price fluctuated a lot on Mon & Tue although it still dominated the market, and you would like to convert the price in SGD in below way:
highest = ('Bitcoin', 10948.52) mon_highest = lambda exchange_rate: highest * exchange_rate highest = ('Bitcoin', 10000) tue_highest = lambda exchange_rate: highest * exchange_rate print("Mon:", mon_highest(1.36)) print("Tue:", tue_highest(1.36))
You want to assign different values in highest variable to calculate the price in another currency, but you would be surprised when checking the result:
Instead of scratching your head to figure out why it does not work, and let’s try another approach. I am going to create a list of converter functions where I pass in the cryptocurrency pair to calculate the price based on the exchange rate supplied. Later I loop through these functions and print out the converted values:
converters = [lambda exchange_rate: crypto * exchange_rate for crypto in cryptocurrencies] for c in converters: print(c(1.36))
I am expecting to see all the prices are converted into local currency based on the exchange rate 1.36, but when running the above code, it gives below result:
Same as the previous behaviour, only the last value was used in lambda function. so why it does not work as intended when I use the lambda in this way?
Runtime data binding
When people come into this issue, it is usually due to a fundamental misunderstanding of the variable binding for Python function. For Python function regardless of normal function or lambda function, the variables used in function are bound at runtime not in definition time. So for our first example, the lambda function only used the highest variable stored in locals() at the moment when it is executed.
With this concept cleared, you shall be able to understand the behavior of the output from above two examples, only the latest values at execution time were used in the lambda function.
To fix this issue, we just need a minor change to our original code to pass in the variable in the function definition as default value to the argument. For instance, below is the fix for the first example:
mon_highest = lambda exchange_rate, highest = highest: highest * exchange_rate tue_highest = lambda exchange_rate, highest = highest: highest * exchange_rate
Below is the fix for the second example:
converters = [lambda exchange_rate, crypto = crypto: crypto * exchange_rate for crypto in cryptocurrencies]
You may wonder why must use lambda in above two examples, indeed they do not necessarily require a lambda function. For the first example, since you need to call the function more than once, you should just use normal function instead just to be more careful when you need any variable from outside of the function.
And for the second example, it can be simply replaced with a list comprehension as per below:
list(map(lambda crypto: crypto * 1.36, cryptocurrencies))
Lambda function provides convenience for writing tiny functions for the one-time use, and make your code concise. But it is also highly restricted due to the one line of expression, as you cannot use multiple statements, exception handling and conditions etc. Whatever lambda does, you can definitely use a normal function to replace. The only thing matters is about the readability, so you will need to evaluate whether it is the best scenario to use lambda, and bear in mind about the variable binding.