Python Lambda

The lambdas are anonymous one line functions in Python that can be used to perform tasks which don't require regular Python functions. Some of the most useful features of Python lambdas are...

  1. Lambda function can take any number of arguments
  2. Lambda functions are very short although can be very encryptic
  3. Lambda functions can be used to return function objects
  4. Lambda functions are restricted to only a single expression

Below snippet shows the basic syntax of a Python function. The below function takes "argument" and returns it back.

In [1]:
# normal function that returns a value
def functionName(argument):
    return argument

Lambda Function Definion

Example of above function "functionName" using Lambda

In [2]:
lambda argument : argument
Out[2]:
<function __main__.<lambda>(argument)>

Note the syntax of the above function. Lambda functions have no name. They are defined and used on the fly. We can't reuse them in the form defined above. The first 'argument' is the argument of the function and the 2nd 'argument' is the returned value.

Example: - Lambda function that returns the twice of the given input.

In [3]:
lambda x : x * 2
Out[3]:
<function __main__.<lambda>(x)>

But you must be wondering, how to provide the input to above lambda function!

Calling the Lambda Function

To use the function, you can surround it with parenthesis and provide the parameters between paranthesis as shown below.

In [4]:
(lambda x : x * 2) (5)
Out[4]:
10

This is how it works...

In [5]:
# (lambda x: x * 2 )(5) = lambda 5 : 5 * 2 = 5 * 2 = 10

There is another way to provide an argument to the lambda function. We can assign the lambda function to a variable and then pass the argument to that variable as shown below...

In [6]:
double = lambda x : x * 2
# and call it like so : 
double(5)
Out[6]:
10

Ofcourse above lambda function and below function are equivalent.

In [7]:
# the function double is equivalent to : 
def double(x):
    return x * 2

Lambda Functions With Mutiple Arguments

Python Lambda functions can have multipe parameters separated by commas (,) here is an example...

In [8]:
pairs = lambda  x , y : "P( x = "+str(x)+" , y = "+ str(y)+" )"
pairs(1,2)
Out[8]:
'P( x = 1 , y = 2 )'

There is a shorthand way of calling Python lambdas function that is without assigning a name to the function.

You can simply do this :

In [9]:
lambda a , b : a * b
Out[9]:
<function __main__.<lambda>(a, b)>

There will be no conflict and you can call the above lambda function with the arguments like this...

In [10]:
_(2,3)
Out[10]:
6

Note the underscore in the above syntax. Underscore refers to the lambda function that we just described above.

Ofcourse, you can use IIFE ( Immediately Invoked Function Expression ) syntax too.

In [11]:
(lambda a , b : a * b) (2,3)
Out[11]:
6

High order Functions

We can use function inside lambda. Below snippet is an example of lambda function inside another lambda function.

In [12]:
# we can use a function as a parameter of lambda : 
myfunction = lambda  param , func : param + func(param)
# and call it like so : 
myfunction(3,lambda  x : x**2)
Out[12]:
12

In the above snippet, we passed outer lambda function two parameters - param and another lambda function (func)

In [13]:
myfunction(4,lambda x : x - 1)
Out[13]:
7

Python lambda and Regular Functions

In [14]:
import dis
div = lambda x,y : x / y
type(div)
Out[14]:
function
In [15]:
dis.dis(div)
  2           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_TRUE_DIVIDE
              6 RETURN_VALUE
In [16]:
div
Out[16]:
<function __main__.<lambda>(x, y)>
In [17]:
# applaying same thing for a normal function: 
import dis
def div(x,y):
    return x / y
type(div)
Out[17]:
function
In [18]:
dis.dis(div)
div
  4           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_TRUE_DIVIDE
              6 RETURN_VALUE
Out[18]:
<function __main__.div(x, y)>

Lambda function Restrictions

Lambda functions throws similar errors as Python regular functions do. For example, below snippet will throw string multiplication error "can't multiply sequence by non-int of type 'str'"

In [19]:
type_error = lambda str1,str2 : str1 * str2
type_error("hello","world")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-19-17effbb67a31> in <module>
      1 type_error = lambda str1,str2 : str1 * str2
----> 2 type_error("hello","world")

<ipython-input-19-17effbb67a31> in <lambda>(str1, str2)
----> 1 type_error = lambda str1,str2 : str1 * str2
      2 type_error("hello","world")

TypeError: can't multiply sequence by non-int of type 'str'

Statements inside Lambda Function

We can't add statments in lambda function as shown below.

In [20]:
(lambda x : assert x > 0)(1)
  File "<ipython-input-20-9947f5245b98>", line 1
    (lambda x : assert x > 0)(1)
                     ^
SyntaxError: invalid syntax

However we can use parenthesis to achieve the statement effect.

In below snippet, note expression (x>0 and + or '-') would translate to if x > 0 then return '+' otherwise return '-'

In [21]:
(lambda x :  (x>0 and '+' or '-'))(-5)
Out[21]:
'-'

Type Annotation

Hinting doesn't work on lambda functions. It only works on normal functions.

Below regular Python function takes 'string' and 'integer' as two parameters but returns output as string.

In [22]:
def function(param:str,i : int)-> str:
    return param * str(i)

In lambda function, if you specify type hints, you will end up getting a syntaxError...

In [23]:
lambda param:str , i : int : param * i
  File "<ipython-input-23-c1c4c22920e0>", line 1
    lambda param:str , i : int : param * i
                               ^
SyntaxError: invalid syntax

*args and **kwargs in Python Lambda

As we described above in the 'multiple arguments section' of this post, Python lambda function can take multiple arguments but lambda functions can also take arguments using *arg and **kwargs

In [24]:
(lambda p1 , p2 , p3 : (p1 + p2  + p3)/3)(1,2,3)
Out[24]:
2.0
In [25]:
(lambda p1 , p2 , p3 = 3 : (p1 + p2  + p3)/3)(1,2)
Out[25]:
2.0

*args example

In [26]:
(lambda *args : sum(args)/len(args))(1,2,3)
Out[26]:
2.0

**kwargs example

In [27]:
(lambda **kwargs : sum(kwargs.values())/len(kwargs))(one = 1, two = 2, three = 3)
Out[27]:
2.0

Python lambdas initialize arguments example

In [28]:
(lambda p1 , p2=0 , p3=0 : (p1 + p2  + p3)/3 ) ( 1 , p2=2 , p3=3)
Out[28]:
2.0

Decorator in Python Lambda Function

Let us see first, how decorators in regular Python functions work. Here is an example...

In [29]:
# Defining a decorator
def trace(f):
    def wrap(*args, **kwargs):
        print(f"[TRACE] function name: {f.__name__}, arguments: {args}, kwargs: {kwargs}")
        return f(*args, **kwargs)

    return wrap

# Applying decorator to a function
@trace
def double(x):
    return x * 2

# Calling the decorated function
double(3)
[TRACE] function name: double, arguments: (3,), kwargs: {}
Out[29]:
6

Check out below example of application of decorator to a lambda function. Notice in the below snippet, how we have wrapped the lambda function inside the trace decorator function. Order of brackets is very important.

In [30]:
print((trace(lambda x: x * 2))(3))
[TRACE] function name: <lambda>, arguments: (3,), kwargs: {}
6

Python lambda with map and filter

Lambda is regularly used with built-in functions like map or filter.

Python map

map iterates the function through a list or set. The function could be regular Python function or lambda function.

In the below example,lambda function x: x + 5 is applied on list of numbers (0,4)

In [31]:
list(map(lambda x : x + 5 ,range(5)))
Out[31]:
[5, 6, 7, 8, 9]

Python filter

In the below example,lambda function x: x > 0 is applied on list of numbers [-5,-2,1,0,3,5]

In [32]:
list(filter(lambda x : x>0,[-5,-2,1,0,3,5]))
Out[32]:
[1, 3, 5]

Decorators in lamda with Python map

In below snippet, map() is taking two arguments. The first one is the decorator function around lambda function i.e.

trace(lambda x: x * 2) and second argument is range(3).

The map() will run the decorated lambda function 3 times as shown below.

In [33]:
list(map(trace(lambda x: x * 2), range(3)))
[TRACE] function name: <lambda>, arguments: (0,), kwargs: {}
[TRACE] function name: <lambda>, arguments: (1,), kwargs: {}
[TRACE] function name: <lambda>, arguments: (2,), kwargs: {}
Out[33]:
[0, 2, 4]

Testing Python Lambda with Unittest

In [ ]:
import unittest
double = lambda x : x * 2

class Test(unittest.TestCase):
    def test_1(self):
        self.assertEqual(double(1),2)
    
    def test_2(self):
        self.assertEqual(double(2),4)
    
    def test_10(self):
        self.assertEqual(double(10),11) # this test will fail

if __name__ == '__main__':
    unittest.main(verbosity=2)
In [36]:
double = lambda x : x * 2
double.__doc__ = """Doubles the number entred in the parameters :
>>> double(1)
2

>>> double(2.0)
4.0

>>> double(10)
20
"""

if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)
Trying:
    double(1)
Expecting:
    2
ok
Trying:
    double(2.0)
Expecting:
    4.0
ok
Trying:
    double(10)
Expecting:
    20
ok
16 items had no tests:
    __main__
    __main__.Test
    __main__.Test.test_1
    __main__.Test.test_10
    __main__.Test.test_2
    __main__._16
    __main__._2
    __main__._3
    __main__._9
    __main__.div
    __main__.function
    __main__.functionName
    __main__.myfunction
    __main__.pairs
    __main__.trace
    __main__.type_error
1 items passed all tests:
   3 tests in __main__.double
3 tests in 17 items.
3 passed and 0 failed.
Test passed.

Python Lambda raise Exception

Here is how regular Python function raises exception.

In [37]:
def throw(excep): 
    raise excep

Here is how lambda functions can raise exception.

In [38]:
(lambda : throw(Exception("my error")))()
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-38-2dcbdacea675> in <module>
----> 1 (lambda : throw(Exception("my error")))()

<ipython-input-38-2dcbdacea675> in <lambda>()
----> 1 (lambda : throw(Exception("my error")))()

<ipython-input-37-5de1b89d45c1> in throw(excep)
      1 def throw(excep):
----> 2     raise excep

Exception: my error

Lambda Cryptic Code Using underscore

Let us look at below example. In here (lambda : * 2) _ refers to a variable or parameter.

In [39]:
(lambda _ : _  * 2)(11)
Out[39]:
22

Let us take at following more cryptic code example. In below code, there are two Python lambda functions. The parameter _ is being used inside both lambda functions.

In [40]:
(lambda _ : list(map(lambda _ : _  * 2,_)))(range(11))
Out[40]:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
In [41]:
# the underscore (_) is the variable 
# to simplify, the function should be like this : 
(lambda myList : list(map(lambda element : element  * 2,myList)))(range(11))
Out[41]:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Here is how it works.

lambda 0 : list(map(lambda : *2,0)) \ lambda 0 : list(0) \ 0

lambda 1: list(map(lambda : *2, 1)) \ lambda 1: list(2) \ 2 \ ....

Here is the above code in a regular Pythonic way...

In [42]:
# regular functions will make it easy due to their multiline format
def doubleItems(myList):
    double = lambda x : x * 2
    return map(double , myList)
list(doubleItems(range(11)))
Out[42]:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Python lambda using Key function

In [43]:
# let's sort a list of strings that have a char a space and an int based ont he value of the integer
myList =["a 1","b 45","x 11","r 16"]
print(sorted(myList))
print(sorted(myList,key = lambda x : int(x[2:])))
['a 1', 'b 45', 'r 16', 'x 11']
['a 1', 'x 11', 'r 16', 'b 45']

Timeit lambda function

In [44]:
from timeit import timeit
timeit(lambda :sum(range(99999)), number=10)
# this silution is cleaner and more readable
Out[44]:
0.013398005161434412