10 Simple Yet Not Simple Lambda Functions

10 Simple Yet Not Simple Lambda Functions #

Hello, I am Jingxiao.

In the previous section, we learned about “conventional” functions in Python, which have a wide range of uses. However, besides conventional functions, you may also come across some “unconventional” functions in code. These functions are often very short, just one line, and have a cool name - lambda, yes, this is the anonymous function.

Anonymous functions also play an important role in practical work. Using anonymous functions correctly can make our code more concise and readable. In this lesson, we will continue our journey through Python functions and learn about this simple yet complex anonymous function together.

Basics of Lambda Functions #

First of all, what is a lambda function? Here is the format of a lambda function:

lambda argument1, argument2,... argumentN : expression

As we can see, the keyword for a lambda function is lambda, followed by a series of parameters separated by commas, and then a colon to separate the parameters from the expression composed of these parameters. Let’s look at a few examples to see how it works:

square = lambda x: x**2
square(3)

Output:

9

In this case, the lambda function takes only one parameter x and returns the square of x. So when the input is 3, the output is 9. If we were to write this lambda function as a regular function, it would look like this:

def square(x):
    return x**2
square(3)

Output:

9

As we can see, both the lambda function and the regular function return a function object, and their usage is very similar. However, there are a few differences.

First, lambda is an expression, not a statement.

  • An expression is used to express something using a series of “formulas”, such as x + 2, x**2, etc.
  • A statement always completes some functionality, such as the assignment statement x = 1 completing the assignment, the print statement print(x) completing the printing, the conditional statement if x < 0: completing the selection, etc.

Therefore, lambda can be used in places where a regular function def cannot be used. For example, lambda can be used inside a list, whereas a regular function def cannot:

[(lambda x: x*x)(x) for x in range(10)]
# Output:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Another example is that lambda can be used as a parameter for certain functions, but a regular function def cannot:

l = [(1, 20), (3, 0), (9, 10), (2, -1)]
l.sort(key=lambda x: x[1]) # Sort by the second element of the tuples in the list
print(l)
# Output:
[(2, -1), (3, 0), (9, 10), (1, 20)]

A regular function def has to be called using its function name, so it must be defined first. But as an expression, a lambda function object doesn’t need a name.

Second, the body of a lambda function can only consist of a single line of simple expression and cannot be expanded into a multi-line code block.

This is actually a design consideration. The reason why lambda was introduced in Python is to separate responsibilities between lambda functions and regular functions: lambda focuses on simple tasks, while regular functions handle more complex multi-line logic. Guido van Rossum, the founder of Python, explained this in an article. If you’re interested, you can read it here.

Why use anonymous functions? #

In theory, any place where anonymous functions (lambda) are used in Python can be replaced with equivalent alternative expressions. A Python program can be written without using any anonymous functions. However, in some cases, using lambda functions can greatly simplify code complexity and improve code readability.

Generally speaking, there are a few reasons why we use functions:

  1. Reduce code repetition.
  2. Modularize code.

For the first point, if your program contains the same code in different places, we usually write this common code as a function and give it a name for easy calling in the corresponding places.

For the second point, if a block of code is intended to achieve a specific functionality, but it is too long and reduces code readability when written together, we usually write this block of code as a separate function and then call it.

However, consider this scenario. You need a function, but it is very short and can be completed in just one line. Furthermore, it is only called once in the program. In this case, do you still need to define and name it like a regular function?

The answer is no. In this case, the function can be anonymous, and you only need to define and use it at the appropriate location to make the anonymous function work.

For example, if you want to square all elements in a list, and this operation is only needed once in your program, you can use a lambda function to represent it as follows:

squared = map(lambda x: x**2, [1, 2, 3, 4, 5])

If you use a regular function, it would be expressed as the following lines of code:

def square(x):
    return x**2

squared = map(square, [1, 2, 3, 4, 5])

Let me explain briefly. In the function map(function, iterable), the first parameter is a function object, and the second parameter is a traversable collection. It represents applying the function to each element of iterable. By comparing the two examples, we can clearly see that the lambda function makes the code more concise and clear.

Here’s another example. In a Python Tkinter GUI application, suppose we want to implement a simple functionality: create a button that, when clicked, prints out a message. If we use a lambda function, it can be represented as follows:

from tkinter import Button, mainloop
button = Button(
    text='This is a button',
    command=lambda: print('being pressed')) # call the lambda function when clicked
button.pack()
mainloop()

If we use a regular function with def, it would require more code:

from tkinter import Button, mainloop

def print_message():
    print('being pressed')

button = Button(
    text='This is a button',
    command=print_message) # call the lambda function when clicked
button.pack()
mainloop()

Clearly, the code using anonymous functions is much more concise and also more in line with Python programming habits.

Functional Programming in Python #

Finally, let’s take a look at the functional programming features of Python, which are closely related to the lambda anonymous function we discussed today.

Functional programming refers to a code structure where each block is immutable and composed of pure functions. Here, pure functions refer to functions that are independent from each other, have no impact on one another, and always produce the same output for the same input without any side effects.

Let’s take a simple example. Suppose we have a list and we want to double the values of each element. We can write it like this:

def multiply_2(l):
    for index in range(0, len(l)):
        l[index] *= 2
    return l

In this code, it is not in the form of a pure function because the values of the elements in the list are changed. If we call the multiply_2() function multiple times, we will get different results each time. To make it a pure function, we need to write it in the following form, creating a new list and returning it:

def multiply_2_pure(l):
    new_list = []
    for item in l:
        new_list.append(item * 2)
    return new_list

The advantages of functional programming lie primarily in the robustness and ease of debugging and testing of programs due to the pure and immutable characteristics of functions. The main disadvantage is the constraints and difficulty in writing code. However, Python is not a purely functional programming language like some languages (e.g., Scala), but it does provide some functional programming features that are worth exploring and learning.

Python mainly provides a few functions for functional programming: map(), filter(), and reduce(), usually used in conjunction with lambda anonymous functions. These are things you need to master, and I will introduce them one by one.

First, let’s talk about the map(function, iterable) function, which we mentioned in the previous example. It applies function to each element in iterable and returns a new iterable. For example, to multiply each element in a list by 2, you can use map like this:

l = [1, 2, 3, 4, 5]
new_list = map(lambda x: x * 2, l)  # [2, 4, 6, 8, 10]

Let’s take map() as an example to demonstrate the performance of the functional programming interface provided by Python. Using the same list example, it can also be implemented using a for loop and list comprehension. Let’s compare their speeds:

python3 -mtimeit -s 'xs=range(1000000)' 'map(lambda x: x*2, xs)'
2000000 loops, best of 5: 171 nsec per loop

python3 -mtimeit -s 'xs=range(1000000)' '[x * 2 for x in xs]'
5 loops, best of 5: 62.9 msec per loop

python3 -mtimeit -s 'xs=range(1000000)' 'l = []' 'for i in xs: l.append(i * 2)'
5 loops, best of 5: 92.7 msec per loop

As you can see, map() is the fastest. This is because the map() function is directly implemented in C and does not need to be invoked indirectly by the Python interpreter at runtime. It also has many internal optimizations, making it the fastest.

Next, let’s talk about the filter(function, iterable) function, which is similar to the map function. function is also a function object. The filter() function applies function to each element in iterable and returns True or False. It then returns a new iterable composed of the elements for which function returns True.

For example, if I want to return all even numbers from a list, I can write it like this:

l = [1, 2, 3, 4, 5]
new_list = filter(lambda x: x % 2 == 0, l)  # [2, 4]

Finally, let’s look at the reduce(function, iterable) function, which is usually used for cumulative operations on a collection.

Similarly, function is a function object that takes two parameters: each element in iterable and the result of the previous call. It performs calculations using function and returns a single value as the result.

For example, if I want to calculate the product of elements in a list, I can use the reduce() function like this:

l = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, l)  # 1 * 2 * 3 * 4 * 5 = 120

Of course, similar to filter(), reduce() can also be implemented using for loops or list comprehension.

Generally speaking, when we want to perform some operations on elements in a collection and the operations are very simple, such as addition or accumulation, we prefer using functions like map(), filter(), reduce(), or list comprehension. As for the choice between these two methods:

  • In the case of a large amount of data, such as in machine learning applications, we generally prefer the functional programming representation, as it is more efficient.

  • In the case of a small amount of data and if you want the code to be more Pythonic, then list comprehension is also a good choice.

However, if you need to perform more complex operations on elements in a collection, considering code readability, we usually use for loops, as they are more clear and understandable.

Summary #

In this lesson, we have learned about the lambda anonymous function in Python, whose main purpose is to reduce code complexity. It is important to note that lambda is an expression, not a statement; it can only be written in a single line form and does not support multiple lines in terms of syntax. The typical use case for anonymous functions is when a program needs to use a function to complete a simple task and that function is only called once.

Furthermore, we have also been introduced to functional programming in Python, specifically the commonly used functions: map(), filter(), and reduce(). We have compared their performance with other forms like for loops and comprehensions, and it is evident that these functions have the best performance efficiency.

Thinking Questions #

Finally, I would like to leave you with two thinking questions.

Question 1: If you were asked to sort a dictionary by its values in descending order, how would you do it? Take the following code snippet as an example and think about it.

d = {'mike': 10, 'lucy': 2, 'ben': 30}

Question 2: In your actual work or studies, what scenarios have you encountered where anonymous functions are used?

Feel free to write down your answers and thoughts in the comment section, discuss with me, and also feel free to share this article with your colleagues and friends.