Functional programming is a programming paradigm in which the primary method of computation is evaluation of functions. In this tutorial, you’ll explore functional programming in Python.
Functional programming typically plays a fairly small role in Python code. But it’s good to be familiar with it. At a minimum, you’ll probably encounter it from time to time when reading code written by others. You may even find situations where it’s advantageous to use Python’s functional programming capabilities in your own code.
In this tutorial, you’ll learn:
lambda keywordmap(), filter(), and reduce()A pure function is a function whose output value follows solely from its input values, without any observable side effects. In functional programming, a program consists entirely of evaluation of pure functions. Computation proceeds by nested or composed function calls, without changes to state or mutable data.
The functional paradigm is popular because it offers several advantages over other programming paradigms. Functional code is:
Many programming languages support some degree of functional programming. In some languages, virtually all code follows the functional paradigm. Haskell is one such example. Python, by contrast, does support functional programming but contains features of other programming models as well.
While it’s true that an in-depth description of functional programming is somewhat complex, the goal here isn’t to present a rigorous definition but to show you what you can do by way of functional programming in Python.
To support functional programming, it’s useful if a function in a given programming language has two abilities:
Python plays nicely in both these respects. As you’ve learned previously in this series, everything in a Python program is an object. All objects in Python have more or less equal stature, and functions are no exception.
In Python, functions are first-class citizens. That means functions have the same characteristics as values like strings and numbers. Anything you would expect to be able to do with a string or number you can do with a function as well.
For example, you can assign a function to a variable. You can then use that variable the same as you would use the function itself:
Python
The assignment another_name = func on line 8 creates a new reference to func() named another_name. You can then call the function by either name, func or another_name, as shown on lines 5 and 9.
You can display a function to the console with print(), include it as an element in a composite data object like a list, or even use it as a dictionary key:
Python
In this example, func() appears in all the same contexts as the values "cat" and 42, and the interpreter handles it just fine.
For present purposes, what matters is that functions in Python satisfy the two criteria beneficial for functional programming listed above. You can pass a function to another function as an argument:
Python
Here’s what’s happening in the above example:
inner() as an argument to outer().outer(), Python binds inner() to the function parameter function.outer() can then call inner() directly via function.This is known as function composition.
When you pass a function to another function, the passed-in function sometimes is referred to as a callback because a call back to the inner function can modify the outer function’s behavior.
A good example of this is the Python function sorted(). Ordinarily, if you pass a list of string values to sorted(), then it sorts them in lexical order:
Python
However, sorted() takes an optional key argument that specifies a callback function that can serve as the sorting key. So, for example, you can sort by string length instead:
Python
sorted() can also take an optional argument that specifies sorting in reverse order. But you could manage the same thing by defining your own callback function that reverses the sense of len():
Python
You can check out How to Use sorted() and .sort() in Python for more information on sorting data in Python.
Just as you can pass a function to another function as an argument, a function can also specify another function as its return value:
Python
Here’s what’s going on in this example:
outer() defines a local function inner().outer() passes inner() back as its return value.outer() is assigned to variable function.Following this, you can call inner() indirectly through function, as shown on line 12. You can also call it indirectly using the return value from outer() without intermediate assignment, as on line 15.
As you can see, Python has the pieces in place to support functional programming nicely. Before you jump into functional code, though, there’s one more concept that will be helpful for you to explore: the lambda expression.
lambdaFunctional programming is all about calling functions and passing them around, so it naturally involves defining a lot of functions. You can always define a function in the usual way, using the def keyword as you have seen in previous tutorials in this series.
Sometimes, though, it’s convenient to be able to define an anonymous function on the fly, without having to give it a name. In Python, you can do this with a lambda expression.
The syntax of a lambda expression is as follows:
Python
The following table summarizes the parts of a lambda expression:
| Component | Meaning |
|---|---|
lambda |
The keyword that introduces a lambda expression |
<parameter_list> |
An optional comma-separated list of parameter names |
: |
Punctuation that separates <parameter_list> from <expression> |
<expression> |
An expression usually involving the names in <parameter_list> |
The value of a lambda expression is a callable function, just like a function defined with the def keyword. It takes arguments, as specified by <parameter_list>, and returns a value, as indicated by <expression>.
Here’s a quick first example:
Python
The statement on line 1 is just the lambda expression by itself. On line 2, Python displays the value of the expression, which you can see is a function.
The built-in Python function callable() returns True if the argument passed to it appears to be callable and False otherwise. Lines 4 and 5 show that the value returned by the lambda expression is in fact callable, as a function should be.
In this case, the parameter list consists of the single parameter s. The subsequent expression s[::-1] is slicing syntax that returns the characters in s in reverse order. So this lambda expression defines a temporary, nameless function that takes a string argument and returns the argument string with the characters reversed.
The object created by a lambda expression is a first-class citizen, just like a standard function or any other object in Python. You can assign it to a variable and then call the function using that name:
Python
This is functionally—no pun intended—equivalent to defining reverse() with the def keyword:
Python
The calls on lines 4 and 8 above behave identically.
However, it’s not necessary to assign a variable to a lambda expression before calling it. You can also call the function defined by a lambda expression directly:
Python
Here’s another example:
Python
In this case, the parameters are x1, x2, and x3, and the expression is x1 + x2 + x3 / 3. This is an anonymous lambda function to calculate the average of three numbers.
As another example, recall above when you defined a reverse_len() to serve as a callback function to sorted():
Python
You could use a lambda function here as well:
Python
A lambda expression will typically have a parameter list, but it’s not required. You can define a lambda function without parameters. The return value is then not dependent on any input parameters:
Python
Note that you can only define fairly rudimentary functions with lambda. The return value from a lambda expression can only be one single expression. A lambda expression can’t contain statements like assignment or return, nor can it contain control structures such as for, while, if, else, or def.
You learned in the previous tutorial on defining a Python function that a function defined with def can effectively return multiple values. If a return statement in a function contains several comma-separated values, then Python packs them and returns them as a tuple:
Python
This implicit tuple packing doesn’t work with an anonymous lambda function:
Python
But you can return a tuple from a lambda function. You just have to denote the tuple explicitly with parentheses. You can also return a list or a dictionary from a lambda function:
Python
A lambda expression has its own local namespace, so the parameter names don’t conflict with identical names in the global namespace. A lambda expression can access variables in the global namespace, but it can’t modify them.
There’s one final oddity to be aware of. If you find a need to include a lambda expression in a formatted string literal (f-string), then you’ll need to enclose it in explicit parentheses:
Python
Now you know how to define an anonymous function with lambda. For further reading on lambda functions, check out How to Use Python Lambda Functions.
Next, it’s time to delve into functional programming in Python. You’ll see how lambda functions are particularly convenient when writing functional code.
Python offers two built-in functions, map() and filter(), that fit the functional programming paradigm. A third, reduce(), is no longer part of the core language but is still available from a module called functools. Each of these three functions takes another function as one of its arguments.
map()The first function on the docket is map(), which is a Python built-in function. With map(), you can apply a function to each element in an iterable in turn, and map() will return an iterator that yields the results. This can allow for some very concise code because a map() statement can often take the place of an explicit loop.
map() With a Single IterableThe syntax for calling map() on a single iterable looks like this:
Python
map(<f>, <iterable>) returns in iterator that yields the results of applying function <f> to each element of <iterable>.
Here’s an example. Suppose you’ve defined reverse(), a function that takes a string argument and returns its reverse, using your old friend the [::-1] string slicing mechanism:
Python
If you have a list of strings, then you can use map() to apply reverse() to each element of the list:
Python
But remember, map() doesn’t return a list. It returns an iterator called a map object. To obtain the values from the iterator, you need to either iterate over it or use list():
Python
Iterating over iterator yields the items from the original list animals, with each string reversed by reverse().
In this example, reverse() is a pretty short function, one you might well not need outside of this use with map(). Rather than cluttering up the code with a throwaway function, you could use an anonymous lambda function instead:
Python
If the iterable contains items that aren’t suitable for the specified function, then Python raises an exception:
Python
In this case, the lambda function expects a string argument, which it tries to slice. The third element in the list, 3.14159, is a float object, which isn’t sliceable. So a TypeError occurs.
Here’s a somewhat more real-world example: In the tutorial section on built-in string methods, you encountered str.join(), which concatenates strings from an iterable, separated by the specified string:
Python
This works fine if the objects in the list are strings. If they aren’t, then str.join() raises a TypeError exception:
Python
One way to remedy this is with a loop. Using a for loop, you can create a new list that contains string representations of the numbers in the original list. Then you can pass the new list to .join():
Python
However, because map() applies a function to each object of a list in turn, it can often eliminate the need for an explicit loop. In this case, you can use map() to apply str() to the list objects before joining them:
Python
map(str, [1, 2, 3, 4, 5]) returns an iterator that yields the list of string objects ["1", "2", "3", "4", "5"], and you can then successfully pass that list to .join().
Although map() accomplishes the desired effect in the above example, it would be more Pythonic to use a list comprehension to replace the explicit loop in a case like this.
map() With Multiple IterablesThere’s another form of map() that takes more than one iterable argument:
Python
map(<f>, <iterable1>, <iterable2>, ..., <iterablen>) applies <f> to the elements in each <iterablei> in parallel and returns an iterator that yields the results.
The number of <iterablei> arguments specified to map() must match the number of arguments that <f> expects. <f> acts on the first item of each <iterablei>, and that result becomes the first item that the return iterator yields. Then <f> acts on the second item in each <iterablei>, and that becomes the second yielded item, and so on.
An example should help clarify:
Python
In this case, f() takes three arguments. Correspondingly, there are three iterable arguments to map(): the lists [1, 2, 3], [10, 20, 30], and [100, 200, 300].
The first item returned is the result of applying f() to the first element in each list: f(1, 10, 100). The second item returned is f(2, 20, 200), and the third is f(3, 30, 300), as shown in the following diagram:
The return value from map() is an iterator that yields the list [111, 222, 333].
Again in this case, since f() is so short, you could readily replace it with a lambda function instead:
Python
This example uses extra parentheses around the lambda function and implicit line continuation. Neither is necessary, but they help make the code easier to read.
filter()filter() allows you to select or filter items from an iterable based on evaluation of the given function. It’s called as follows:
Python
filter(<f>, <iterable>) applies function <f> to each element of <iterable> and returns an iterator that yields all items for which <f> is truthy. Conversely, it filters out all items for which <f> is falsy.
In the following example, greater_than_100(x) is truthy if x > 100:
Python
In this case, greater_than_100() is truthy for items 111, 222, and 333, so these items remain, whereas 1, 2, and 3 are discarded. As in previous examples, greater_than_100() is a short function, and you could replace it with a lambda expression instead:
Python
The next example features range(). range(n) produces an iterator that yields the integers from 0 to n - 1. The following example uses filter() to select only the even numbers from the list and filter out the odd numbers:
Python
Here’s an example using a built-in string method:
Python
Remember from the previous tutorial on string methods that s.isupper() returns True if all alphabetic characters in s are uppercase and False otherwise.
reduce()reduce() applies a function to the items in an iterable two at a time, progressively combining them to produce a single result.
reduce() was once a built-in function in Python. Guido van Rossum apparently rather disliked reduce() and advocated for its removal from the language entirely. Here’s what he had to say about it:
So now
reduce(). This is actually the one I’ve always hated most, because, apart from a few examples involving+or*, almost every time I see areduce()call with a non-trivial function argument, I need to grab pen and paper to diagram what’s actually being fed into that function before I understand what thereduce()is supposed to do. So in my mind, the applicability ofreduce()is pretty much limited to associative operators, and in all other cases it’s better to write out the accumulation loop explicitly. (Source)
Guido actually advocated for eliminating all three of reduce(), map(), and filter() from Python. One can only guess at his rationale. As it happens, the previously mentioned list comprehension covers the functionality provided by all these functions and much more. You can learn more by reading When to Use a List Comprehension in Python.
As you’ve seen, map() and filter() remain built-in functions in Python. reduce() is no longer a built-in function, but it’s available for import from a standard library module, as you’ll see next.
To use reduce(), you need to import it from a module called functools. This is possible in several ways, but the following is the most straightforward:
Python
Following this, the interpreter places reduce() into the global namespace and makes it available for use. The examples you’ll see below assume that this is the case.
reduce() With Two ArgumentsThe most straightforward reduce() call takes one function and one iterable, as shown below:
Python
reduce(<f>, <iterable>) uses <f>, which must be a function that takes exactly two arguments, to progressively combine the elements in <iterable>. To start, reduce() invokes <f> on the first two elements of <iterable>. That result is then combined with the third element, then that result with the fourth, and so on until the list is exhausted. Then reduce() returns the final result.
Guido was right when he said the most straightforward applications of reduce() are those using associative operators. Let’s start with the plus operator (+):
Python
This call to reduce() produces the result 15 from the list [1, 2, 3, 4, 5] as follows:
This is a rather roundabout way of summing the numbers in the list! While this works fine, there’s a more direct way. Python’s built-in sum() returns the sum of the numeric values in an iterable:
Python
Remember that the binary plus operator also concatenates strings. So this same example will progressively concatenate the strings in a list as well:
Python
Again, there’s a way to accomplish this that most would consider more typically Pythonic. This is precisely what str.join() does:
Python
Now consider an example using the binary multiplication operator (*). The factorial of a positive integer n is defined as follows:
You can implement a factorial function using reduce() and range() as shown below:
Python
Once again, there’s a more straightforward way to do this. You can use factorial() provided by the standard math module:
Python
As a final example, suppose you need to find the maximum value in a list. Python provides the built-in function max() to do this, but you could use reduce() as well:
Python
Notice that in each example above, the function passed to reduce() is a one-line function. In each case, you could have used a lambda function instead:
Python
This is a convenient way to avoid placing an otherwise unneeded function into the namespace. On the other hand, it may be a little harder for someone reading the code to determine your intent when you use lambda instead of defining a separate function. As is often the case, it’s a balance between readability and convenience.
reduce() With an Initial ValueThere’s another way to call reduce() that specifies an initial value for the reduction sequence:
Python
When present, <init> specifies an initial value for the combination. In the first call to <f>, the arguments are <init> and the first element of <iterable>. That result is then combined with the second element of <iterable>, and so on:
Python
Now the sequence of function calls looks like this:
You could readily achieve the same result without reduce():
Python
As you’ve seen in the above examples, even in cases where you can accomplish a task using reduce(), it’s often possible to find a more straightforward and Pythonic way to accomplish the same task without it. Maybe it’s not so hard to imagine why reduce() was removed from the core language after all.
That said, reduce() is kind of a remarkable function. The description at the beginning of this section states that reduce() combines elements to produce a single result. But that result can be a composite object like a list or a tuple. For that reason, reduce() is a very generalized higher-order function from which many other functions can be implemented.
For example, you can implement map() in terms of reduce():
Python
You can implement filter() using reduce() as well:
Python
In fact, any operation on a sequence of objects can be expressed as a reduction.
Functional programming is a programming paradigm in which the primary method of computation is evaluation of pure functions. Although Python is not primarily a functional language, it’s good to be familiar with lambda, map(), filter(), and reduce() because they can help you write concise, high-level, parallelizable code. You’ll also see them in code that others have written.
In this tutorial, you learned:
lambdamap(), filter(), and reduce()With that, you’ve reached the end of this introductory series on the fundamentals of working with Python. Congratulations! You now have a solid foundation for making useful programs in an efficient, Pythonic style.
If you’re interested in taking your Python skills to the next level, then you can check out some more intermediate and advanced tutorials. You can also check out some Python project ideas to start putting your Python superpowers on display. Happy coding!