How to Write Python Lambda Functions
Python is one of the most versatile languages available.
Many programming languages today are becoming more and more Pythonic due to this flexibility and versatility.
Lambda functions are one of the features that are being cherished by other languages which make programming smarter and much more efficient. Lambda functions are also called anonymous functions or function literals.
They also may or may not be explicitly pointed by a variable or be named.
Before we dive into more details, let's take a look at what Lambda functions are and how they compare with regular functions in structure.
Table of Contents
You can skip to a specific section of this Python lambda function tutorial using the table of contents below:
Quick Look into Regular vs Lambda Functions
If we consider a regular function, this is how it would look like.
def add_one(x):
return x+1
This function would take one argument and would add 1 to it and return. If we run add_one(2)
it would return 3.
Let’s see how we can write the same with a lambda function.
lambda arguments : body
lambda x:x+1
If we look at the above, it has the lambda keyword, an argument (or multiple arguments), and the body of the function.
However, if we are to run this, we have to encapsulate the whole statement with parenthesis and pass an argument as follows.
(lambda x:x+1)(2)
If we run the above, it will return 3.
You must be wondering how you would use a lambda function elsewhere in the code without having to explicitly write it there.
There are three ways to do that. We will go over them one by one.
Use the Underscore (Not for Python Modules)
This method is not encouraged to be used. However, if you are using the Python shell or a Jupyter Notebook, the underscore will point to the last lambda expression that is defined.
lambda x:x+1
_(2)
If the above code is run, it will return 3. Since this does not work with modules, there are two more ways that can be considered to call a lambda function.
Naming the Lambda Function
Instead of writing lambda x:x+1
anonymously, it can be named.
my_func = lambda x:x+1
Now you can call the following anywhere in the code and use the lambda function.
my_func(2)
This will return 3 as the output.
Having the Lambda Function Inside a Regular Function
This is the most common way of using lambda functions. Some larger regular functions require code snippets which are not significant enough to be defined as a separate function explicitly. In circumstances like these, lambda functions can be used.
def square_func(a):
#do some operations here
return (lambda x : x*x)(a)
If we run square_func(2)
, it will return 4.
It should also be noted that the above function is only made simple for demonstration purposes. In order for a lambda function to be embedded in regular functions, they are expected to be more complex in practice.
That was a quick look into Python lambda functions.
Lambda Functions – Examples
In this section, we will take a look at a few more examples of using lambda functions.
Multiple Arguments with Lambda Functions
If we go over the syntax of a lambda function, it is as follows.
lambda arguments : body
If only one argument is present,
lambda x:x+1
Let us write a lambda function to add two numbers together.
add_numbers = lambda x,y : x+y
add_numbers(2,4)
If this is run, it will return 6.
Returning Lambda Functions as Objects
Let us consider user-defined multiplier objects for doubling, tripling, and quadrupling values.
We can first write a function to return a lambda function with the required multiplier embedded.
def multiply_by(n):
return lambda x : x*n
Now we can create multiplier objects as follows.
value_doubler = multiply_by(2)
value_tripler = multiply_by(3)
value_quadrupler = multiply_by(4)
Now we can use our multiplier objects to work.
value_doubler(5)
value_tripler(5)
value_quadrupler(5)
If the above are run one by one, they will return 10, 15, and 20.
Passing Functions as Arguments into Lambda Functions
Let us define a few lambda functions to perform operations such as add one, subtract one, and square.
add_one = lambda x:x+1
subtract_one = lambda x:x-1
get_square = lambda x:x*x
A function that takes in another function as an argument can be called higher-order functions. We can write a higher-order function to get a result by inputting the function as follows.
get_result = lambda x, func : func(x)
Then we can run the function as below by running the following separately.
get_result(4, add_one)
get_result(4, subtract_one)
get_result(4, get_square)
Each would output 5, 3, 16 respectively.
The above are the most common ways of using lambda functions. All the other usages are either combinations or derivatives of these.
A Deeper Look into Regular vs Lambda Functions
Regular functions somewhat take more lines of code than a lambda function. However, as developers or programmers, one should be aware of the underlying mechanisms or the extreme cases of using one over the other. The following is a comparison made between a few key aspects of both these function types.
Syntax
We already know the structure of both regular and lambda functions.
def add_one(x):
return x+1
lambda arguments : body
lambda x:x+1
As it can be seen, the lambda structure has a few distinctive features. For starters, it is written as a single line with expressions. They also can be immediately called then and there, unlike regular functions. Let us go over these, one by one.
Single Line
(lambda x :
'Positive' if x>0 else 'Zero or Negative')(5)
A lambda function in Python contains a single line with an expression in its body. Although while writing the lambda function, it can span over multiple lines. However, since there is only one expression, it theoretically is a one-liner function.
Moreover, a single expression does also mean that there can be no statements. Statements are lines of code that execute instructions to perform an action. Expressions are lines of code that evaluate to a value. Let us try to perform a simple instruction.
y = 10
(lambda x: y=x )(5)
In this, we are trying to change the value of y to 5. However, running this returns a SyntaxError: invalid syntax
.
Immediately Invoked Function Expression
IIFE is a type of function that executes as soon as they are defined. Lambda functions can be immediately invoked as soon as they are defined, unlike regular functions. This is also why lambda functions are sometimes called anonymous functions or expressions.
Argument Passing
In terms of argument passing, lambda functions function the same way regular functions do. They do support both positional, named, and default arguments. Furthermore, they also support both keyworded and non-keyworded variable lists.
The following are examples of using them with lambda functions.
Positional Arguments
(lambda x,y: x+y)(5,10)
Keyword Arguments
(lambda x,y: x+y)(x=5,y=10)
Default Arguments
(lambda x=5,y=10: x+y)()
(lambda x,y=10: x+y)(5)
*args
These arguments are used to pass a varying number of arguments into a function.
(lambda *args: sum(args))(5, 10, 15, 20)
**kwargs
These are used to pass a varying number of keyworded arguments into a function.
(lambda **kwargs: sum(kwargs.values()))(x=5, y=10, z=15)
Functionality of the Interpreter
The underlying functionality of the functions at the face of the interpreter plays a huge role in the execution time functions consume. Therefore, as a developer, one should be aware of it to present the most optimized code.
This can be achieved by using the ‘dis module’, the disassembler for Python bytecode.
Let us take a look at the bytecode of a regular function.
import dis
def add_one(x):
return x+1
dis.dis(add_one)
We would get the outcome as,
These are the set of instructions the Python interpreter uses to perform the task.
Let us look at the bytecode of a lambda function which does the same task.
import dis
dis.dis( lambda x : x+1)
Output:
<<<<<<< HEAD
=======
e7caeb1fc432d1ec6b667471cf9d5504eeee136f
Bytecodes of both functions are identical. This leads us to the conclusion that in spite of the different syntaxes of the functions, their underlying functionality and the mechanisms are alike.
Traceback (in Exceptions)
Let us write a regular function to add an integer to a string
def add_string(x):
return x+''
If we try to execute add_string(10), it will throw TypeError: unsupported operand types.
Its traceback would be
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in add_string
If you use an IPython shell or a notebook, it would give the following error.
<ipython-input-xx-xxxxx> in add_string(x)
Let us do the same with a lambda function.
add_string = lambda x : x+''
add_string(10)
This would throw the same error. However, its traceback would say
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
If you use an IPython shell or a notebook, it would give the following error.
< ipython-input-xx-xxxxx > in <lambda>(x) .
It is visible that although both functions were named, in the traceback of the lambda function, it explicitly mentions lambda instead of the function name. Therefore, when it comes to tracing back errors, the usage of regular functions would be much more exact unlike with lambda functions.
Decorators
Functions in Python are objects. They can be passed as arguments and returned from functions. Decorators in Python enable the functionality of modifying the behavior of a function. We can wrap a function with another and temporarily modify its behavior.
Let us define a simple decorator function and a decorated function.
def func_1(f):
def dec(*args):
print('Decorator')
return f(args)
return dec
@func_1
def func_2(num):
return 'This is the decorated function: ' + str(num)
In this, func_1
is the decorator and func_2
is the decorated function. It can be identified by the @func_1. The above is equivalent to the following.
def func_1(f):
def dec(*args):
print('Decorator')
return f(args)
return dec
def func_2(num):
return 'This is the decorated function: ' + str(num)
func_2 = func_1(func_2)
If we run func_2(10), if would output,
Decorator
'This is the decorated function: (10,)'
Let us do the same for a lambda function. However, note that it is not supported to @func_1 before a lambda function. Therefore, the right way is to define both the decorator and the lambda function and pass the lambda function into the decorated function.
def func_1(f):
def dec(*args):
print('Decorator')
return f(args)
return dec
func_2 = lambda num :'This is the decorated function: ' + str(num)
Now that we have both functions defined, let us run it.
func_1(func_2)(10)
Note that the argument for the lambda function is outside the decorated function.
Output:
Decorator
'This is the decorated function: (10,)'
Closures
A closure in Python is a functional object that can be used to sustain values in nested scopes.
Let us try to understand how closures work in regular functions. Consider the following nested function.
def func_1(x):
x=x*2
def func_2(y):
print('x = ' +str(x)+' y = '+str(y))
return x + y
return func_2
for i in range(3):
func_3 = func_1(i)
print('func_3('+str(i) + ') = '+str(func_3(i)))
print()
For this code, output is,
x = 0 y = 0
func_3(0) = 0
x = 2 y = 1
func_3(1) = 3
x = 4 y = 2
func_3(2) = 6
It is understandable that although calling of func_3(i) has no whatsoever connection it its previous iteration in the for loop. However, the value of y has access to its lasting value from its previous scope. This is a phenomenon induced by the closure.
Let us try to do the same with lambda function.
def func_1(x):
x=x*2
return lambda y: x+y
for i in range(3):
func_3 = func_1(i)
print('func_3('+str(i) + ') = '+str(func_3(i)))
This would result in the following:
func_3(0) = 0
func_3(1) = 3
func_3(2) = 6
This is the same output as func_3(i) in the regular function example. This means the lambda function behaves the same way as a regular function does.
Code Testing
Code testing, such as unit testing, is used to test how the code functions. These usually come in handy when codes are refactored. unittest
and doctest
are two of the most common libraries used for Python testing. Usually, test cases are written for all functions and asserted.
Let us see how the unittest module works with regular functions.
import unittest
def add_num(a,b):
return a+b
class TestSum(unittest.TestCase):
def test_sum(self):
self.assertEqual(6, add_num(3,3) )
if __name__ == '__main__':
unittest.main(argv=['first-arg-is-ignored'], exit=False)
Let us see how this is written for lambda functions.
import unittest
add_num = lambda a,b: a+b
class TestSum(unittest.TestCase):
def test_sum(self):
self.assertEqual(6, add_num(3,3) )
if __name__ == '__main__':
unittest.main(argv=['first-arg-is-ignored'], exit=False)
For both these tests, the output is
Ran 1 test in 0.000s
OK
The doctest module will also perform the same way for both lambda and regular functions.
When to Use Lambda Functions
Lambda expressions are a topic of debate for developers. Some are for it, and some are against it. Developers who are against back their facts with readability issues, breach of functional concept, and the syntactical issues.
However, lambda functions are considered to be mutually accepted in the following scenarios in the programming community.
map() function
sum_name = lambda a,b: a+b
my_map = map(sum_name, ('Red', 'Blue', 'Green'), ('John', 'Freddie', 'Gunther'))
list(my_map)
Output:
['RedJohn', 'BlueFreddie', 'GreenGunther']
filter() function
marks = [51, 21, 78, 18, 81, 90]
my_filter = lambda x: x if x>75 else 0
passed_students = filter(my_filter, marks)
list(passed_students)
Output:
[78, 81, 90]
Functions with key as a parameter – Ex: max(), min(), sort()
student_num = ['2006/3281','2007/6234','2005/6384','2004/2341']
newest_student = max(student_num, key = lambda x: int(x[:4]))
newest_student
Output:
‘2007/6234’
timeit.timeit
timeit
is useful in calculating how much time it takes for a task to be done.
Following is the usual syntax of timeit with its usage:
import timeit
import_statement = "from math import sqrt"
my_code = "sqrt(10000000)"
timeit.timeit(setup = import_statement
stmt = my_code,
number = 10000)
Output: (Will vary from device to device)
0.0012792159977834672
However instead of this, we can use lambda functions as follows.
import timeit
from math import sqrt
my_code = "sqrt(10000000)"
timeit.timeit(lambda : sqrt(10000000), number = 10000)
Output: (Will vary from device to device)
0.0012739029856584966
Other:
- Mapping actions to events. Ex: UI events
- Dynamic modifications of a class. Ex: Monkey Patching
When to Avoid Using Lambda Functions
Lambda expressions are believed to be going against Python standards in the following cases. These are not necessarily about the code’s functionality but also about readability. Avoid using lambda expressions with the following as much as possible.
Raising Exceptions
def custom_exception():
raise Exception('Throwing my exception')
(lambda: custom_exception())()
Output:
Traceback (most recent call last):
File "xxx.py", line 3, in <module>
(lambda: custom_exception())()
File "xxx.py", line 3, in <lambda>
(lambda: custom_exception())()
File "xxx.py", line 2, in custom_exception
raise Exception('Throwing my exception')
Exception: Throwing my exception
Although the code seems to be functioning, it is advised to use regular functions in this scenario.
Using Underscore in Lambda
Underscores in Python are used to reference throwaway variables. Lambda functions are already bringing in a bit of obscurity to the code and adding an underscore in the lambda function would only worsen it.
Class Methods
It should also be avoided writing lambda for class methods in Python. Although the interpreter has no issue with this, it is considered a bad practice.
Map Filter Reduce Instead of Lambda Functions
It is already our understanding that lambda expressions are not the favorite of every developer. Some are for it, and some are against it. This is why it is best to be knowledgeable about other functions that can be used instead of lambda expressions in some cases.
map()
Simply, the map() is a function that executes another function individually for each item in and returns the output as an iterable.
def sum_name(a, b):
return a+b
my_map = map(sum_name, ('Red', 'Blue', 'Green'), ('John', 'Freddie', 'Gunther'))
list(my_map)
Output:
['RedJohn', 'BlueFreddie', 'GreenGunther']
filter()
This function returns an iterable object after filtering through a function of choice.
marks = [51, 21, 78, 18, 81, 90]
def my_filter(x):
if x > 75:
return x
passed_students = filter(my_filter, marks)
list(passed_students)
Output:
[78, 81, 90]
reduce()
Syntax of reduce() is reduce(function, sequence) which is similar to both map() and filter(). The functionality of reduce() is as follows.
The first two objects of the sequence are considered and input to the function to obtain a result. Then that result and the next object in the sequence are considered and input to the function to obtain the next result. This is continued until all objects in the sequence are processed.
The following is a simple example
import functools
my_list = [1,2,3,4,5,6]
def adder(x,y):
return x+y
functools.reduce(adder,my_list)
Output:
21
Final Thoughts
Lambda functions are a great way to write anonymous functions and also to avoid the use of unnecessary lines. They are a very effective and clever way to optimize the code as well. In spite of its flexibility, they are a topic of debate. This is mainly because the readability of code is as much important as how optimized the code is.
Lambda functions have been in Python since 1994 and are perfectly Pythonic. If a developer is concerned about keeping the code short, lambda functions will definitely come in handy. However, it is always best to only use lambda functions the way they are intended to by the community and the standards.