Table of Contents
Functional programming is a programming paradigm in which the primary method of computation is the evaluation of functions. But how does Python support functional programming?
In this tutorial, you’ll learn:
lambda
keywordmap()
,filter()
, andreduce()
Functional programming typically plays a minor role in Python code, but it’s still good to be familiar with it. You’ll probably encounter it from time to time when reading code written by others. And you may even find situations where it’s advantageous to use Python’s functional programming capabilities in your own code.
Get Your Code:Click here to download the free sample code that shows you when and how to use functional programming in Python.
Take the Quiz: Test your knowledge with our interactive “Functional Programming in Python: When and How to Use It” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Functional Programming in Python: When and How to Use ItIn this quiz, you'll test your understanding of functional programming in Python. You'll revisit concepts such as functions being first-class citizens in Python, the use of the lambda keyword, and the implementation of functional code using map(), filter(), and reduce().
Apure function is a function whose output value follows solely from its input values without any observableside effects. Infunctional programming, a program consists primarily of the evaluation of pure functions. Computation proceeds by nested orcomposed 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 offunctional 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 beneficial if afunction in a given programming language can do these two things:
Python plays nicely in both respects. Everything in Python is anobject, and all objects in Python have more or less equal stature. Functions are no exception.
In Python, functions arefirst-class citizens. This means that functions have the same characteristics as values likestrings andnumbers. Anything you would expect to be able to do with a string or number, you can also do with a function.
For example, you can assign a function to a variable. You can then use that variable the same way you would use the function itself:
1>>>deffunc(): 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 assignmentanother_name = func
on line 8 creates a new reference tofunc()
namedanother_name
. You can then call the function by either of the two names,func
oranother_name
, as shown on lines 5 and 9.
You can display a function to the console withprint()
, include it as an element in a composite data object like alist, or even use it as adictionary key:
>>>deffunc():...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"
and42
, and the interpreter handles it just fine.
Note: What you can or can’t do with an object in Python depends to some extent on context. Some operations work for certain object types but not for others.
For example, you can add two integer objects orconcatenate two string objects with the plus operator (+
), but the plus operator isn’t defined for function objects.
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:
1>>>definner(): 2...print("I am function inner()!") 3... 4 5>>>defouter(function): 6...function() 7... 8 9>>>outer(inner)10I am function inner()!
Here’s what’s happening in the above example:
inner()
as an argument toouter()
.outer()
, Python bindsinner()
to the function parameterfunction
.outer()
can then callinner()
directly withfunction
.This is known asfunction composition. Keep in mind that you’re passing the functionobject as an argument. If you wouldcall the function object using parentheses, then you wouldn’t pass the function object but instead its return value.
Note: Python provides a shortcut notation called adecorator to facilitate wrapping one function inside another. For more information about decorators, check out thePrimer on Python Decorators tutorial.
When you pass a function to another function, the passed-in function is sometimes referred to as acallback because acall back to theinner function can modify the outer function’s behavior.
A good example of this is the Python functionsorted()
. Ordinarily, if you pass a list of string values tosorted()
, then it sorts them in lexical order:
>>>animals=["ferret","vole","dog","gecko"]>>>sorted(animals)['dog', 'ferret', 'gecko', 'vole']
However,sorted()
takes an optionalkey
argument that specifies a callback function that can serve as the sorting key. So, for example, you can sort by string length instead:
>>>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 oflen()
:
>>>animals=["ferret","vole","dog","gecko"]>>>sorted(animals,key=len,reverse=True)['ferret', 'gecko', 'vole', 'dog']>>>defreverse_len(s):...return-len(s)...>>>sorted(animals,key=reverse_len)['ferret', 'gecko', 'vole', 'dog']
Note: For more information on sorting data in Python, you can check out theHow to Usesorted()
and.sort()
in Python tutorial.
Just as you can pass a function to another function as an argument, a function can also specify another function as its return value:
1>>>defouter(): 2...definner(): 3...print("I am function inner()!") 4...# Function outer() returns function inner() 5...returninner 6... 7 8>>>function=outer() 9>>>function10<function outer.<locals>.inner at 0x7f18bc85faf0>11>>>function()12I am function inner()!1314>>>outer()()15I am function inner()!
Here’s what’s going on in this example:
outer()
defines the local functioninner()
.outer()
passesinner()
back as its return value.outer()
to the variablefunction
.Following this, you can callinner()
indirectly throughfunction
, as shown on line 11. You can also call it indirectly using the return value fromouter()
without intermediate assignment, as on line 14.
As you can see, Python has the pieces in place to support functional programming nicely. But before you jump into functional code, there’s one more concept that will be helpful for you to explore: thelambda
expression.
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 thedef
keyword.
Sometimes, it’s convenient to be able to define ananonymous function on the fly without having to give it a name. In Python, you can do this with alambda
expression.
Note: The termlambda comes fromlambda calculus, a formal system of mathematical logic for expressing computation based on function abstraction and application.
The syntax of alambda
expression is as follows:
lambda<parameter_list>:<expression>
The following table summarizes the parts of alambda
expression:
Component | Meaning |
---|---|
lambda | The keyword that introduces alambda 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> , which represents the lambda function’s return value |
The value of alambda
expression is a callable function, just like a function defined with thedef
keyword. It takes arguments, as specified by<parameter_list>
, and returns a value, as indicated by<expression>
.
Here’s a quick first example:
1>>>lambdas:s[::-1] 2<function <lambda> at 0x7fef8b452e18> 3 4>>>callable(lambdas:s[::-1]) 5True
The statement on line 1 is just thelambda
expression by itself. On line 2, thePython REPL displays the value of the expression, which is a function.
Note: The termlambda expression refers to the syntax used to define a lambda function.The termlambda function refers to the actual anonymous function object created by a lambda expression.
The built-in Python functioncallable()
returnsTrue
if the argument passed to it appears to be callable andFalse
otherwise. Lines 4 and 5 show that the value returned by thelambda
expression is in fact callable, as a function should be.
In this case, the parameter list consists of the single parameters
. The subsequent expressions[::-1]
is slicing syntax thatreturns the characters ins
in reverse order. So thislambda
expression defines a temporary, nameless function that takes a string argument and returns the argument string with the characters reversed.
The object created by alambda
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:
>>>reverse=lambdas:s[::-1]>>>reverse("I am a string")'gnirts a ma I'
This is functionally—no pun intended—equivalent to definingreverse()
with thedef
keyword:
1>>>defreverse(s): 2...returns[::-1] 3... 4>>>reverse("I am a string") 5'gnirts a ma I' 6 7>>>reverse=lambdas:s[::-1] 8>>>reverse("I am a string") 9'gnirts a ma I'
The calls on lines 4 and 8 behave in the exact same way.
Note: In general, you shouldn’t give names to lambda functions. If you need a named function that you can refer to elsewhere, then you should define it withdef
.
However, it’s not necessary to assign a variable to alambda
expression before calling it. You can also call the function defined by alambda
expression directly:
>>>(lambdas:s[::-1])("I am a string")'gnirts a ma I'
You wrapped the lambda expression into parentheses to clarify where it ends, then appended another set of parentheses and passed"I am a string"
as an argument to your anonymous function. Python assigned the string argument to the parameters
, then your lambda function reversed the string and returned the result.
Here’s another example that builds on the same concept but is more complex because it uses multiple arguments in the lambda expression:
>>>(lambdax1,x2,x3:(x1+x2+x3)/3)(9,6,6)7.0>>>(lambdax1,x2,x3:(x1+x2+x3)/3)(1.4,1.1,0.5)1.0
In this case, the parameters arex1
,x2
, andx3
, and the expression isx1 + x2 + x3 / 3
. This is an anonymouslambda
function to calculate the average of three numbers.
Note:Readability counts! As you can see, it’s possible to build complex lambda expressions. However, it gets challenging to keep track of what they do.
In the above example, you should instead define a function and give it a descriptive name, such asaverage_of_three_numbers()
.
The true advantage of using lambda expressions shows when you use them for short and straightforward logic. Recall when you definedreverse_len()
above to serve as a callback function tosorted()
:
>>>animals=["ferret","vole","dog","gecko"]>>>defreverse_len(s):...return-len(s)...>>>sorted(animals,key=reverse_len)['ferret', 'gecko', 'vole', 'dog']
Instead of definingreverse_len
, you could write a short and straightforwardlambda
expression:
>>>animals=["ferret","vole","dog","gecko"]>>>sorted(animals,key=lambdas:-len(s))['ferret', 'gecko', 'vole', 'dog']
Alambda
expression will typically have a parameter list, but it’s not required. You can define alambda
function without parameters. The return value is then not dependent on any input parameters:
>>>forty_two_producer=lambda:42>>>forty_two_producer()42
Note that you can only define fairly rudimentary functions withlambda
. The return value from alambda
expression can only be one single expression. Alambda
expression can’t contain statements likeassignment orreturn
, nor can it contain control structures such asfor
,while
,if
,else
, ordef
.
Note: While lambda expressions can’t contain any conditional statements, theycan containconditional expressions:
>>>(lambdax:"even"ifx%2==0else"odd")(2)'even'>>>(lambdax:"even"ifx%2==0else"odd")(3)'odd'
Using conditional expressions allows you to build more complex logic into your lambda expressions, but in most cases, it’ll be better to define a named function instead.
Whendefining a Python function withdef
, you can effectively return multiple values. If areturn
statement in a function contains several comma-separated values, then Python packs them and returns them as atuple:
>>>defpower_tuple(x):...returnx,x**2,x**3...>>>power_tuple(3)(3, 9, 27)>>>type(power_tuple(3))<class 'tuple'>
This implicit tuple packing doesn’t work with an anonymouslambda
function:
>>>(lambdax: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>", line1, in<module>NameError:name 'x' is not defined
But you can explicitly return a tuple from alambda
function. You just have to denote the tuple with parentheses. You can also return a list or a dictionary from alambda
function:
>>>(lambdax:(x,x**2,x**3))(3)(3, 9, 27)>>>(lambdax:[x,x**2,x**3])(3)[3, 9, 27]>>>(lambdax:{1:x,2:x**2,3:x**3})(3){1: 3, 2: 9, 3: 27}
Alambda
expression has its own localnamespace, so the parameter names don’t conflict with identical names in the global namespace. Alambda
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 alambda
expression in a formatted string literal, orf-string, then you’ll need to enclose it in explicit parentheses:
>>>print(f"-{lambdas: s[::-1]} -") File"<stdin>", line1print(f"-{lambdas: s[::-1]} -")^^^^^^^^^SyntaxError:f-string: lambda expressions are not allowed⮑ without parentheses>>>print(f"-{(lambdas:s[::-1])} -")- <function <lambda> at 0x7f97b775fa60> ->>>print(f"-{(lambdas:s[::-1])('I am a string')} -")- gnirts a ma I -
Now you know how to define an anonymous function withlambda
. Next, it’s time to delve into functional programming in Python. You’ll see howlambda
functions are particularly convenient when writing functional code.
Python offers two built-in functions,map()
andfilter()
, that fit the functional programming paradigm. A third function,reduce()
, is no longer part of the core language but is still available in a module calledfunctools
. Each of these three functions takes another function as one of its arguments.
map()
The first function on the docket ismap()
, which is aPython built-in function. Withmap()
, you can apply a function to each element in aniterable in turn. Themap()
function will return aniterator that yields the results. This can allow for some very concise code because amap()
statement can often take the place of an explicit loop.
map()
With a Single IterableYou can callmap()
with one iterable or with many iterables. You’ll start by looking at the syntax for callingmap()
on asingle iterable:
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 definedreverse()
, which is a function that takes a string argument and returns its reverse using your old friend the[::-1]
string slicing mechanism:
>>>defreverse(s):...returns[::-1]...>>>reverse("I am a string")'gnirts a ma I'
If you have a list of strings, then you can usemap()
to applyreverse()
to each element of the list:
>>>animals=["cat","dog","hedgehog","gecko"]>>>map(reverse,animals)<map object at 0x7fd3558cbef0>
But remember,map()
doesn’t return a list. It returns amap object, which is aniterator. To obtain the values from the iterator, you need to eitheriterate over it or uselist()
:
>>>iterator=map(reverse,animals)>>>foranimaliniterator:...print(animal)...tacgodgohegdehokceg>>>iterator=map(reverse,animals)>>>list(iterator)['tac', 'god', 'gohegdeh', 'okceg']
Iterating overiterator
yields the items from the original list,animals
, with each string reversed byreverse()
. In the second example, you collect all items that the iterator yields into a new list usinglist()
.
In this example,reverse()
is a fairly short function and one you might not need outside of this use withmap()
. Rather than cluttering up the code with a throwaway function, you could use an anonymouslambda
function instead:
>>>animals=["cat","dog","hedgehog","gecko"]>>>iterator=map(lambdas:s[::-1],animals)>>>list(iterator)['tac', 'god', 'gohegdeh', 'okceg']>>># Combining it all into one line:>>>list(map(lambdas: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 anexception:
>>>list(map(lambdas:s[::-1],["cat","dog",3.14159,"gecko"]))Traceback (most recent call last): File"<stdin>", line1, in<module> File"<stdin>", line1, in<lambda>TypeError:'float' object is not subscriptable
In this case, thelambda
function expects a string argument, which it tries to slice. The third element in the list,3.14159
, isafloat
object. Because it isn’t sliceable, aTypeError
occurs.
Here’s a somewhat more real-world example. One of Python’sbuilt-in string methods,str.join()
, concatenates strings from an iterable, separated by the string that you call it on:
>>>"+".join(["cat","dog","hedgehog","gecko"])'cat+dog+hedgehog+gecko'
This works fine if the objects in the list are strings. If they aren’t, thenstr.join()
raises aTypeError
exception:
>>>"+".join([1,2,3,4,5])Traceback (most recent call last): File"<stdin>", line1, in<module>TypeError:sequence item 0: expected str instance, int found
One way to remedy this is with a loop. Using afor
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()
:
>>>number_strings=[]>>>fornumberin[1,2,3,4,5]:...number_strings.append(str(number))...>>>number_strings['1', '2', '3', '4', '5']>>>"+".join(number_strings)'1+2+3+4+5'
However, becausemap()
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 usemap()
to applystr()
to the elements in the list before joining them:
>>>"+".join(map(str,[1,2,3,4,5]))'1+2+3+4+5'
The call tomap(str, [1, 2, 3, 4, 5])
returns an iterator. This iterator, when consumed, yields the string representations of each element in the list[1, 2, 3, 4, 5]
, resulting in["1", "2", "3", "4", "5"]
.
The"+".join()
method then takes this iterator and concatenates its elements with a"+"
delimiter, producing the string"1+2+3+4+5"
. It works and allows for less code without the need to write an explicit loop.
However, althoughmap()
accomplishes the desired effect in the example above, it would be morePythonic to use alist comprehension instead of an explicit loop in a case like this.
map()
With Multiple IterablesThere’s another way that you can usemap()
when you’re passing more than one iterable after the function argument:
map(<f>,<iterable₁>,<iterable₂>,...,<iterableₙ>)
In this example,map(<f>, <iterable
1
>, <iterable
2
>, ..., <iterable
n
>)
applies<f>
to the elements in each<iterable
i
>
in parallel and returns an iterator that yields the results.
The number of<iterable
i
>
arguments specified tomap()
must match the number of arguments that<f>
expects.<f>
acts on the first item of each<iterable
i
>
, and that result becomes the first item that the return iterator yields. Then,<f>
acts on the second item in each<iterable
i
>
, and that becomes the second yielded item, and so on.
A detailed example should help clarify:
>>>defadd_three(a,b,c):...returna+b+c...>>>list(map(add_three,[1,2,3],[10,20,30],[100,200,300]))[111, 222, 333]
In this case,add_three()
takes three arguments. Correspondingly, there are three iterable arguments tomap()
. In this case, all are lists:
[1, 2, 3]
[10, 20, 30]
[100, 200, 300]
The first item thatmap()
yields is the result of applyingadd_three()
to the first element in each list:
>>>add_three(1,10,100)111
The second item is the result of calculatingadd_three(2, 20, 200)
, and the third item is the result of calculatingadd_three(3, 30, 300)
. This is shown in the following diagram:
The return value frommap()
is an iterator that yields the items111
,222
, and333
. You again uselist()
to collect these items in a list.
Becauseadd_three()
is so short, you could readily replace it with alambda
function instead:
>>>list(...map(...lambdaa,b,c:a+b+c,...[1,2,3,4],...[10,20,30,40],...[100,200,300,400],...)...)[111, 222, 333, 444]
In this example, you’ve added a fourth element to each iteratble, which yields a fourth sum. Keep in mind that the length of the iterables isn’t relevant for this approach to work. It’s only important that you pass as many iterables as your function takes inputs.
This means that each list could have only one element or one thousand elements—this same approach still works. Try changing the number of elements in each list and running the code another time.
Additionally, this example usesimplicit line continuation inside parentheses. This isn’t necessary, but it helps make the code easier to read.
If you’d like to learn more about processing iterables without a loop usingmap()
, then check out thePython’s map(): Processing Iterables Without a Loop tutorial.
filter()
filter()
allows you to select—orfilter—items from an iterable based on evaluation of the given function. Its function signature is shown below:
filter(<f>,<iterable>)
filter(<f>, <iterable>)
applies function<f>
to each element of<iterable>
and returns an iterator that yields all items for which<f>
istruthy. Conversely, it filters out all items for which<f>
is falsy.
In the following example,greater_than_100(x)
is truthy ifx > 100
:
>>>defgreater_than_100(x):...returnx>100...>>>list(filter(greater_than_100,[1,111,2,222,3,333]))[111, 222, 333]
In this case,greater_than_100()
is truthy for items111
,222
, and333
, so these items remain, whereasfilter()
discards1
,2
, and3
. As in previous examples,greater_than_100()
is a short function, and you could replace it with alambda
expression instead:
>>>list(filter(lambdax:x>100,[1,111,2,222,3,333]))[111, 222, 333]
The next example featuresrange()
.range(n)
produces an iterator that yields the integers from0
ton - 1
. The following example usesfilter()
to select only the even numbers from the list and filter out the odd numbers:
>>>list(range(10))[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>>defis_even(x):...returnx%2==0...>>>list(filter(is_even,range(10)))[0, 2, 4, 6, 8]>>>list(filter(lambdax:x%2==0,range(10)))[0, 2, 4, 6, 8]
You can also usefilter()
with other data types, such as strings. In the next example, you want to filter a list ofanimals
so that only uppercase values remain. You can do that usingfilter()
and a built-in string method, either with a helper function or using a lambda expression:
>>>animals=["cat","Cat","CAT","dog","Dog","DOG","emu","Emu","EMU"]>>>defall_caps(s):...returns.isupper()...>>>list(filter(all_caps,animals))['CAT', 'DOG', 'EMU']>>>list(filter(lambdas:s.isupper(),animals))['CAT', 'DOG', 'EMU']
This works because thestring method.isupper()
returnsTrue
if all alphabetic characters in the string that you call it on are uppercase. If any of the characters aren’t uppercase, then.isupper()
returnsFalse
.
As mentioned, the function that you pass tofilter()
doesn’t need to returnTrue
andFalse
explicitly. It also works with functions that return truthy and falsy values:
>>>animals_and_empty_strings=["","cat","dog","",""]>>>list(filter(lambdas:s,animals_and_empty_strings))['cat', 'dog']
In this example, you used the lambda expressionlambda s: s
as the function argument. This anonymous function returns the string without any changes. Because empty strings (""
) are falsy in Python,filter()
only keeps the non-empty strings which you then use to create a new list,["cat", "dog"]
.
If you want to dive deeper into use cases forfilter()
, then you can read about how toextract values from iterables usingfilter()
.
reduce()
reduce()
applies a function to the items in an iterable two at a time, progressively combining them to produce a single result.
As you learned earlier,reduce()
is no longer part of the core language but was once a built-in function. Apparently,Guido van Rossum—the creator of Python—rather dislikedreduce()
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 of
reduce()
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 the elimination of all three functions,reduce()
,map()
, andfilter()
, as well as lambda expressions from Python. He supported using the more Pythonic list comprehensions andgenerator expressions instead.
Note: Comprehensions cover the functionality provided by these three functions and much more. You can learn more by reading theWhen to Use a List Comprehension in Python tutorial.
As you’ve seen,map()
andfilter()
have remained in Python.reduce()
is no longer a built-in function, but it’s still available forimport from a standard-library module calledfunctools
.
There are several ways to importreduce()
, but the following approach is the most straightforward:
fromfunctoolsimportreduce
When Python executes this line of code, the interpreter placesreduce()
into theglobal namespace and makes it available for use. The examples you’ll see in the next section will importreduce()
fromfunctools
as shown above.
reduce()
With Two ArgumentsThe most straightforwardreduce()
call takes one function and one iterable:
reduce(<f>,<iterable>)
In a call toreduce(<f>, <iterable>)
, the function<f>
must be a function that takes exactly two arguments.reduce()
will then progressively combine the elements in<iterable>
using<f>
. 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 that the most straightforward applications ofreduce()
are those usingassociative operators—for example, the plus operator (+
):
>>>deff(x,y):...returnx+y...>>>fromfunctoolsimportreduce>>>reduce(f,[1,2,3,4,5])15
This call toreduce()
produces the result15
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-insum()
function returns the sum of the numeric values in an iterable:
>>>sum([1,2,3,4,5])15
Remember that the binary plus operator alsoconcatenates strings. So this same example will progressively concatenate the strings in a list as well:
>>>reduce(f,["cat","dog","hedgehog","gecko"])'catdoghedgehoggecko'
Again, there’s a way to accomplish this that many would consider more typically Pythonic usingstr.join()
:
>>>"".join(["cat","dog","hedgehog","gecko"])'catdoghedgehoggecko'
Now consider an example using the binary multiplication operator (*
). Thefactorial of the positive integern
is defined as follows:
You can implement a factorial function usingreduce()
andrange()
as shown below:
>>>defmultiply(x,y):...returnx*y...>>>fromfunctoolsimportreduce>>>deffactorial_with_reduce(n):...returnreduce(multiply,range(1,n+1))...>>>factorial_with_reduce(4)# 1 * 2 * 3 * 424>>>factorial_with_reduce(6)# 1 * 2 * 3 * 4 * 5 * 6720
Once again, there’s a more straightforward way to do this. You can usefactorial()
provided by the standardmath
module:
>>>frommathimportfactorial>>>factorial(4)24>>>factorial(6)720
As a final example, suppose you need to find the maximum value in a list. Python provides the built-in functionmax()
to do this, but you could usereduce()
as well:
>>>max([23,49,6,32])49>>>defgreater(x,y):...returnxifx>yelsey...>>>fromfunctoolsimportreduce>>>reduce(greater,[23,49,6,32])49
Notice that in each of the above examples, the function passed toreduce()
is a one-line function. In each case, you could have used alambda
function instead:
>>>fromfunctoolsimportreduce>>>reduce(lambdax,y:x+y,[1,2,3,4,5])15>>>reduce(lambdax,y:x+y,["cat","dog","hedgehog","gecko"])'catdoghedgehoggecko'>>>deffactorial_with_reduce(n):...returnreduce(lambdax,y:x*y,range(1,n+1))...>>>factorial_with_reduce(4)24>>>factorial_with_reduce(6)720>>>reduce((lambdax,y:xifx>yelsey),[23,49,6,32])49
This may be 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 uselambda
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 callreduce()
that specifies an initial value for the reduction sequence:
reduce(<f>,<iterable>,<initializer>)
When present,<initializer>
specifies an initial value for the combination. In the first call to<f>
, the arguments are<initializer>
and the first element of<iterable>
. That result is then combined with the second element of<iterable>
, and so on:
>>>deff(x,y):...returnx+y...>>>fromfunctoolsimportreduce>>>reduce(f,[1,2,3,4,5],100)# (100 + 1 + 2 + 3 + 4 + 5)115>>># Using lambda:>>>reduce(lambdax,y:x+y,[1,2,3,4,5],100)115
Consider this diagram to better understand the sequence of function calls that Python goes through when you callreduce()
with an initializer:
Again,reduce()
isn’t the only way to make this calculation happen. You could also achieve the same result withoutreduce()
:
>>>sum([1,2,3,4,5],start=100)115
As you’ve seen in the above examples, even in cases where you can accomplish a task usingreduce()
, it’s often possible to find a more straightforward andPythonic way to accomplish the same task without it. Maybe it’s not so hard to understand whyreduce()
was removed from the core language after all.
Despite the fact thatreduce()
isn’t necessary to write your Python code, it’s a remarkable function. The description at the beginning of this section states thatreduce()
combines elements to produce asingle result.
But that result doesn’t have to be a singlevalue, like in the examples shown above. It can also be a composite object like a list or a tuple. For that reason,reduce()
is a very generalizedhigher-order function from which you can implement many other functions.
For example, you can implementmap()
in terms ofreduce()
:
>>>numbers=[1,2,3,4,5]>>>list(map(str,numbers))['1', '2', '3', '4', '5']>>>fromfunctoolsimportreduce>>>defcustom_map(function,iterable):...returnreduce(...lambdaitems,value:items+[function(value)],...iterable,...[],...)...>>>list(custom_map(str,numbers))['1', '2', '3', '4', '5']
You can implementfilter()
usingreduce()
as well:
>>>numbers=list(range(10))>>>numbers[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>>defis_even(x):...returnx%2==0...>>>list(filter(is_even,numbers))[0, 2, 4, 6, 8]>>>fromfunctoolsimportreduce>>>defcustom_filter(function,iterable):...returnreduce(...lambdaitems,value:items+[value]iffunction(value)elseitems,...iterable,...[],...)...>>>list(custom_filter(is_even,numbers))[0, 2, 4, 6, 8]
In fact, you can express any operation on a sequence of objects as a reduction.
At this point, you’ve increased your knowledge aboutreduce()
and know why the Python community decided to hide it away in thefunctools
module. You also better understand how to usereduce()
and where to import it from if you decide to experiment with it.
If you want to learn more about how to move from a functional to a Pythonic coding style, then you can read the dedicated tutorial onPython’s reduce().
Functional programming is a programming paradigm in which the primary method of computation is the evaluation of pure functions. Even though Python isn’t primarily a functional language, you can still write Python following functional programming principles.
To do this, it’s a good idea to be familiar withlambda
,map()
,filter()
, andreduce()
. They can help you write concise, high-level, parallelizable code. You may also see these functions used in code that others have written, so it’s good to understand how they work.
In this tutorial, you learned:
lambda
map()
,filter()
, andreduce()
Incorporating functional programming concepts into your Python code may help you write more efficient, readable, and maintainable programs. Keep experimenting, and don’t hesitate to combine functional programming with other paradigms to create robust and versatile applications.
If you have any questions, comments, or examples of how you’ve used these concepts in your own projects, please share them in the comments section below. Your feedback and experiences can help others in the community learn and grow.
Get Your Code:Click here to download the free sample code that shows you when and how to use functional programming in Python.
Take the Quiz: Test your knowledge with our interactive “Functional Programming in Python: When and How to Use It” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Functional Programming in Python: When and How to Use ItIn this quiz, you'll test your understanding of functional programming in Python. You'll revisit concepts such as functions being first-class citizens in Python, the use of the lambda keyword, and the implementation of functional code using map(), filter(), and reduce().
🐍 Python Tricks 💌
Get a short & sweetPython Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.
AboutJohn Sturtz
John is an avid Pythonista and a member of the Real Python tutorial team.
» More about JohnMasterReal-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
MasterReal-World Python Skills
With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students.Get tips for asking good questions andget answers to common questions in our support portal.
Keep Learning
Already have an account?Sign-In
Almost there! Complete this form and click the button below to gain instant access:
Functional Programming in Python: When and How to Use It (Sample Code)