Introduction

Lambdas: Python’s anonymous functions

Python lambdas are compact, anonymous functions, which are subject to a more restrictive but concise syntax than the regular def Python functions. They are often greeted with trepidation, given the connotations with lambda calculus in mathematics - however, they are in fact perfectly straightforward and open the door to some really interesting functional programming design ideas!

Let’s get right to the point - the basic general syntax for Python lambdas is:

lambda argument_1, argument_2, ..., argument_n: expression

And so immediately we notice the following syntax details:

  1. There is no body to the function i.e. no statement block. Instead a lambda body is restricted to a single expression. This single expression isn’t surrounded by the { and } braces but is instead deliberately “exposed” in order to limit the amount of logic you can fit into the lambda. This means for example that you are effectively forced to use lambda for simple, straightforward, single task functionality - likewise to steer away from even slightly complex constructs such as the if statement.

  2. The single code construct is an expression, not a statement. Consequently a lambda can appear in places where the traditional def function statement, for example as an item inside a list literal, or as a def function signature. Finally it of course also means that you are creating and referencing the lambda function in place within the code - rather than with the def statement which must have already been created elsewhere.

And so lets have a quick comparison of how a traditional def function statement compares with a lambda expression with a more concrete example:

Compare:

def myFunc(x, y, z):
    return x + y + z

to it’s (named) lambda equivalent:

myFunc = lambda x, y, z: z + y + z

or it’s immediately invoked (see below) lambda equivalent:

(lambda x, y, z: x + y + z)(x, y, z)

What we see is that with a lambda expression we have a much shorter single line expression of code (a so called ‘reduction’ in mathematical terms), that can perform the same functionality and achieve the same output as the traditional def function statement. Moreover, it can be defined precisely where it is needed -and- if it the result is needed immediately the result of the lambda can be immediately invoked.

The significance may still be somewhat abstract, but for now consider that this introduces aspects of functional languages (with their origin in mathematics) into Python, a language that is in the main imperative. With functional languages we can make use of a more declarative approach to programming - one that emphasizes abstraction, data transformation, composition and which is stateless. In comparison, imperative languages such as Python, typically embrace the state-based model of computation where program flow is driven stepwise from instruction to instruction and with state built upon (and/or mutated) as the program progresses. The lambda expression (and also the map(), filter() and reduce() expressions) therefore mark the adoption of very different coding design principles to those the Python language uses in the main.

Syntax Rules

Before we get further into a comparison between def function statements and lambda expressions, let’s cover the basic syntax rules that we must follow with lambda.

  1. A lambda expression cannot include statements (such as assert or return).

  2. As mentioned above, lambda expressions must be contained as a single line of execution - no statement blocks!

  3. Since lambda expressions are by definition anonymous functions they do not support type annotations.

  4. A lambda expression can be immediately invoked - just like a JavaScript “Immediately Invoked Function Expression” (IIFE).

1. No Statements

As noted above, a lambda expression cannot include any statements (such as assert or return). Doing so will raise a SyntaxError exception.

For example, the output of the following code snippet would be a SyntaxError:

myFunc = lambda x: return x
print(myFunc(42))

File "<input>", line 1
 (lambda x: return x)(42)
            ^
SyntaxError: invalid syntax

We notice that the interpreter has identified a SyntaxError while parsing the code because it involves the return statement in the body of the lambda.

2. Single Expressions Only

lambda expressions must be contained as a single line of execution. That is, in contrast to a normal def function, a Python lambda function is a single expression. In this way lambda expression provide us a more concise way of defining a function.

Of course, you can still have that single expression spread over several lines by using either parentheses or a multiline string. The result still remains a single expression:

(lambda x:
... (x % 2 and 'odd' or 'even'))(3)
'odd'

The example above runs correctly, despite being spread across two lines, because it is contained in a set of parentheses. But, again, it remains a legal single expression lambda.

3. Type Annotations

4. Immediately Invoked Function Expressions (IIFE)

A code pattern more common to more functional languages such as JavaScript is to immediately execut a Python lambda function.

We’ve already seen an example of this above:

(lambda x, y, z: x + y + z)(1, 2, 3)

This will both define and then immediately call the lambda function with the three arguments 1, 2, and 3. Outside of the Python interpreter, this feature is probably not used in practice. Indeed Python does not encourage using immediately invoked lambda expressions. It simply results as a direct consequence of a lambda expression being callable, unlike the body of a normal function.

Uses for lambda - or - “how not to obfuscate your Python code”

The Python lambda expression may seem a little redundant in the context of the expressive code structure Python usually encourages. Some of the arguments against Python lambda expression are:

  • That it makes your code less clear, and reduces readability.
  • That it imposes a more functional way of thinking into your code design, which when combined with the other imperative aspects risks confusing design ideas.
  • That it introduces a more syntax heavy style of coding.

Furthermore the fact that the body of a lambda has to be a single expression (rather than a series of statements) may appear to place strict limits on how much logic you can access within the lambda. However if you become familiar with how to construct lambda expressions and where they can be used within your code you’ll find that most statements in Python can be re-expressed as lambda expression equivalents. And so by extension, there are many occasions where lambda functions provide clear value to the Python language and developers design ideas.

Shorthand constructs

lambda expressions are regularly used with Python’s built in map(), filter(), and reduce() functions. For example:

list(map(lambda x: x.upper(), ['cat', 'dog', 'cow']))

Returns a list of the form ['CAT', 'DOG', 'COW'].

>>> list(filter(lambda x: 'o' in x, ['cat', 'dog', 'cow']))

Returns a list of the form ['dog', 'cow'].

>>> from functools import reduce
>>> reduce(lambda acc, x: f'{acc} | {x}', ['cat', 'dog', 'cow'])

Returns the string construct cat | dog | cow.

Another example involves embedding lambda functions directly into a Python list. These are often called jump tables, that is, lists or dictionaries of actions to be performed on demand. For example:

myList = [ lambda x: x ** 2,
           lambda x: x ** 3,
           lambda x: x ** 4]

And we might use the above construct as follows:

for func in myList:
    print(func(2))

Which will print 4, 8, 16.

print(myList[0](3))

Which will print 9.

Key Functions

Key functions are higher-order functions that take a parameter key as a named argument. Thus the key receives a function that can be a lambda. This function will then directly influence the algorithm driven by the key function itself.

For example:

sort() # list method
sorted(), min(), max() # built-in functions

And thus, imagine that you wanted to sort a list of IDs, which were represented as strings - where each ID was formed as the concatenation of the string ‘id’ and some number:

ids = ['id1', 'id2', 'id30', 'id3', 'id22', 'id100']

If you were to sort this list with the built-in function sorted(), then by default it would return the list sorted in lexicographic order because the elements in the list are strings. Thus ‘a’ before ‘b’ before ‘c’, etc:

print(sorted(ids)) # Lexicographic sort

Would return the list in lexicographic order: ['id1', 'id100', 'id2', 'id22', 'id3', 'id30'].

But by using lambda expressions we can influence how the sorting process is executed. You can assign a lambda function to the named argument key such that the sorting will use the number associated with the ID:

sorted_ids = sorted(ids, key=lambda x: int(x[2:])) # Integer sort

Which would return: ['id1', 'id2', 'id3', 'id22', 'id30', 'id100'].

Higher-Order Functions

lambda functions are frequently used with higher-order functions, which take one or more functions as arguments or return one or more functions.

A lambda function can be used as a higher-order function by taking a function (normal or lambda) as an argument - such as in the following example:

high_ord_func = lambda x, func: x + func(x)

high_ord_func(2, lambda x: x * x) # Returns 6.

high_ord_func(2, lambda x: x + 3) # Returns 7.

And as we’ll see in a moment, Python provides a few built in higher-order functions in the standard library. There include map(), filter(), functools.reduce(), as well as key functions like sort(), sorted(), min(), and max().

Map

The built-in map() higher-order function takes a function as its first argument and applies this to each of the elements of its second argument, which must be an iterable. Examples of iterables are strings, lists, and tuples. Thus map() will return an iterator corresponding to the transformed collection. For example suppose you wanted to transform a list of strings into a new list with each string capitalized - you could use map() as below:

list(map(lambda x: x.capitalize(), ['cat', 'dog', 'cow']))

Which would return ['Cat', 'Dog', 'Cow'].

Filter

The built-in filter() higher-order function can be converted into a list comprehension. It takes a predicate as a first argument and an iterable as a second argument and then builds an iterator containing all the elements of the initial collection that satisfy the predicate function. For example, to filter all the even numbers out of a given list of integers, we can do the following:

# The predicate.
even = lambda x: x%2 == 0

# The filter() function:
list(filter(even, range(11)))

Which would return the even numbers only as a new list: [0, 2, 4, 6, 8, 10].

Reduce

Since Python 3, reduce() moved from being a built-in function to a function contained within the functools module. As was the case with map() and filter(), its first two arguments are respectively a function and an iterable. It may also take an initializer as a third argument that is used as the initial value of the resulting accumulator.

For each element of the iterable, reduce() applies the function and accumulates the result that is returned when the iterable is exhausted. For example we could apply reduce() to a list of pairs and calculate the sum of just the first item of each pair, as follows:

import functools
pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
functools.reduce(lambda acc, pair: acc + pair[0], pairs, 0)

Which would return the sum of the first item of each pair: 6.

Conclusions

So, if you bind a lambda function to a variable you may well wonder what the exactly distinguishes this from a regular function with a single return statement.

add = lambda x, y: x + y

# versus
def add(x, y):
    return x + y

And the answer is that behind the scenes there is indeed almost no difference between these two. But as we have seen throughout the article, the value of lambda expressions lies in being able to place them in locations where the regular def statement is not allowed. For example to define inline callback functions, or for rapid coding and placement of snippet functionality directly where it is needed rather than requiring it to be defined somewhere else as with the def statement. And finally, in the case of the map(), filter(), and reduce() methods, when these are nested within a list comprehension (the square [ ] brackets) we obtain multiple functionality from single lines of code. Even managing to return to more Pythonic ways of designing code!