Functional Programming in Python: When and How to Use It

Functional Programming in Python: When and How to Use It

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:

  • What the functional programming paradigm entails
  • What it means to say that functions are first-class citizens in Python
  • How to define anonymous functions with the lambda keyword
  • How to implement functional code using map(), filter(), and reduce()

What Is Functional Programming?

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:

  • High level: You’re describing the result you want rather than explicitly specifying the steps required to get there. Single statements tend to be concise but pack a lot of punch.
  • Transparent: The behavior of a pure function depends only on its inputs and outputs, without intermediary values. That eliminates the possibility of side effects, which facilitates debugging.
  • Parallelizable: Routines that don’t cause side effects can more easily run in parallel with one another.

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.

How Well Does Python Support Functional Programming?

To support functional programming, it’s useful if a function in a given programming language has two abilities:

  1. To take another function as an argument
  2. To return another function to its caller

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
 1>>> def func():
 2...     print("I am function func()!")
 3...
 4
 5>>> func()
 6I am function func()!
 7
 8>>> another_name = func
 9>>> another_name()
10I am function func()!

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
>>> def func():
...     print("I am function func()!")
...

>>> print("cat", func, 42)
cat <function func at 0x7f81b4d29bf8> 42

>>> objects = ["cat", func, 42]
>>> objects[1]
<function func at 0x7f81b4d29bf8>
>>> objects[1]()
I am function func()!

>>> d = {"cat": 1, func: 2, 42: 3}
>>> d[func]
2

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
 1>>> def inner():
 2...     print("I am function inner()!")
 3...
 4
 5>>> def outer(function):
 6...     function()
 7...
 8
 9>>> outer(inner)
10I am function inner()!

Here’s what’s happening in the above example:

  • The call on line 9 passes inner() as an argument to outer().
  • Within 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
>>> animals = ["ferret", "vole", "dog", "gecko"]
>>> sorted(animals)
['dog', 'ferret', 'gecko', 'vole']

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
>>> animals = ["ferret", "vole", "dog", "gecko"]
>>> sorted(animals, key=len)
['dog', 'vole', 'gecko', 'ferret']

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
>>> animals = ["ferret", "vole", "dog", "gecko"]
>>> sorted(animals, key=len, reverse=True)
['ferret', 'gecko', 'vole', 'dog']

>>> def reverse_len(s):
...     return -len(s)
...
>>> sorted(animals, key=reverse_len)
['ferret', 'gecko', 'vole', 'dog']

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
 1>>> def outer():
 2...     def inner():
 3...             print("I am function inner()!")
 4...
 5...     # Function outer() returns function inner()
 6...     return inner
 7...
 8
 9>>> function = outer()
10>>> function
11<function outer.<locals>.inner at 0x7f18bc85faf0>
12>>> function()
13I am function inner()!
14
15>>> outer()()
16I am function inner()!

Here’s what’s going on in this example:

  • Lines 2 to 3: outer() defines a local function inner().
  • Line 6: outer() passes inner() back as its return value.
  • Line 9: The return value from 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.

Defining an Anonymous Function With lambda

Functional 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
lambda <parameter_list>: <expression>

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
 1>>> lambda s: s[::-1]
 2<function <lambda> at 0x7fef8b452e18>
 3
 4>>> callable(lambda s: s[::-1])
 5True

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
>>> reverse = lambda s: s[::-1]
>>> reverse("I am a string")
'gnirts a ma I'

This is functionally—no pun intended—equivalent to defining reverse() with the def keyword:

Python
 1>>> def reverse(s):
 2...     return s[::-1]
 3...
 4>>> reverse("I am a string")
 5'gnirts a ma I'
 6
 7>>> reverse = lambda s: s[::-1]
 8>>> reverse("I am a string")
 9'gnirts a ma I'

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
>>> (lambda s: s[::-1])("I am a string")
'gnirts a ma I'

Here’s another example:

Python
>>> (lambda x1, x2, x3: (x1 + x2 + x3) / 3)(9, 6, 6)
7.0
>>> (lambda x1, x2, x3: (x1 + x2 + x3) / 3)(1.4, 1.1, 0.5)
1.0

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
>>> animals = ["ferret", "vole", "dog", "gecko"]

>>> def reverse_len(s):
...     return -len(s)
...
>>> sorted(animals, key=reverse_len)
['ferret', 'gecko', 'vole', 'dog']

You could use a lambda function here as well:

Python
>>> animals = ["ferret", "vole", "dog", "gecko"]
>>> sorted(animals, key=lambda s: -len(s))
['ferret', 'gecko', 'vole', 'dog']

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
>>> forty_two_producer = lambda: 42
>>> forty_two_producer()
42

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
>>> def func(x):
...     return x, x ** 2, x ** 3
...
>>> func(3)
(3, 9, 27)

This implicit tuple packing doesn’t work with an anonymous lambda function:

Python
>>> (lambda x: x, x ** 2, x ** 3)(3)
<stdin>:1: SyntaxWarning: 'tuple' object is not callable; perhaps you missed a comma?
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

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
>>> (lambda x: (x, x ** 2, x ** 3))(3)
(3, 9, 27)
>>> (lambda x: [x, x ** 2, x ** 3])(3)
[3, 9, 27]
>>> (lambda x: {1: x, 2: x ** 2, 3: x ** 3})(3)
{1: 3, 2: 9, 3: 27}

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
>>> print(f"--- {lambda s: s[::-1]} ---")
  File "<stdin>", line 1
    (lambda s)
             ^
SyntaxError: f-string: invalid syntax

>>> print(f"--- {(lambda s: s[::-1])} ---")
--- <function <lambda> at 0x7f97b775fa60> ---
>>> print(f"--- {(lambda s: s[::-1])('I am a string')} ---")
--- gnirts a ma I ---

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.

Applying a Function to an Iterable With 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.

Calling map() With a Single Iterable

The syntax for calling map() on a single iterable looks like this:

Python
map(<f>, <iterable>)

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
>>> def reverse(s):
...     return s[::-1]
...
>>> reverse("I am a string")
'gnirts a ma I'

If you have a list of strings, then you can use map() to apply reverse() to each element of the list:

Python
>>> animals = ["cat", "dog", "hedgehog", "gecko"]
>>> iterator = map(reverse, animals)
>>> iterator
<map object at 0x7fd3558cbef0>

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
>>> iterator = map(reverse, animals)
>>> for i in iterator:
...     print(i)
...
tac
god
gohegdeh
okceg

>>> iterator = map(reverse, animals)
>>> list(iterator)
['tac', 'god', 'gohegdeh', 'okceg']

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
>>> animals = ["cat", "dog", "hedgehog", "gecko"]
>>> iterator = map(lambda s: s[::-1], animals)
>>> list(iterator)
['tac', 'god', 'gohegdeh', 'okceg']

>>> # Combining it all into one line:
>>> list(map(lambda s: s[::-1], ["cat", "dog", "hedgehog", "gecko"]))
['tac', 'god', 'gohegdeh', 'okceg']

If the iterable contains items that aren’t suitable for the specified function, then Python raises an exception:

Python
>>> list(map(lambda s: s[::-1], ["cat", "dog", 3.14159, "gecko"]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
TypeError: 'float' object is not subscriptable

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
>>> "+".join(["cat", "dog", "hedgehog", "gecko"])
'cat+dog+hedgehog+gecko'

This works fine if the objects in the list are strings. If they aren’t, then str.join() raises a TypeError exception:

Python
>>> "+".join([1, 2, 3, 4, 5])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, int found

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
>>> strings = []
>>> for i in [1, 2, 3, 4, 5]:
...     strings.append(str(i))
...
>>> strings
['1', '2', '3', '4', '5']
>>> "+".join(