Introduction
The map()
, filter()
and reduce()
functions bring a bit of functional programming to Python. All three of these are convenience functions that can be replaced with List Comprehensions or loops, but provide a more elegant and short-hand approach to some problems.
Before continuing, we'll go over a few things you should be familiar with before reading about the aforementioned methods:
What is an anonymous function/method or lambda?
An anonymous method is a method without a name, i.e. not bound to an identifier like when we define a method using def method:
.
Note: Though most people use the terms "anonymous function" and "lambda function" interchangeably - they're not the same. This mistake happens because in most programming languages lambdas are anonymous and all anonymous functions are lambdas. This is also the case in Python. Thus, we won't go into this distinction further in this article.
What is the syntax of a lambda function (or lambda operator)?
lambda arguments: expression
Think of lambdas as one-line methods without a name. They work practically the same as any other method in Python, for example:
def add(x,y):
return x + y
Can be translated to:
lambda x, y: x + y
Lambdas differ from normal Python methods because they can have only one expression, can't contain any statements and their return type is a function
object. So the line of code above doesn't exactly return the value x + y
but the function that calculates x + y
.
Why are lambdas relevant to
map()
,filter()
andreduce()
?
All three of these methods expect a function
object as the first argument. This function
object can be a pre-defined method with a name (like def add(x,y)
).
Though, more often than not, functions passed to map()
, filter()
, and reduce()
are the ones you'd use only once, so there's often no point in defining a referenceable function.
To avoid defining a new function for your different map()
/filter()
/reduce()
needs - a more elegant solution would be to use a short, disposable, anonymous function that you will only use once and never again - a lambda.
The map() Function
The map()
function iterates through all items in the given iterable and executes the function
we passed as an argument on each of them.
The syntax is:
map(function, iterable(s))
We can pass as many iterable objects as we want after passing the function
we want to use:
# Without using lambdas
def starts_with_A(s):
return s[0] == "A"
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(starts_with_A, fruit)
print(list(map_object))
This code will result in:
[True, False, False, True, False]
As we can see, we ended up with a new list where the function starts_with_A()
was evaluated for each of the elements in the list fruit
. The results of this function were added to the list sequentially.
A prettier way to do this exact same thing is by using lambdas:
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(lambda s: s[0] == "A", fruit)
print(list(map_object))
We get the same output:
[True, False, False, True, False]
Note: You may have noticed that we've cast map_object
to a list to print each element's value. We did this because calling print()
on a list will print the actual values of the elements. Calling print()
on map_object
would print the memory addresses of the values instead.
The map()
function returns the map_object
type, which is an iterable and we could have printed the results like this as well:
for value in map_object:
print(value)
If you'd like the map()
function to return a list instead, you can just cast it when calling the function:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
result_list = list(map(lambda s: s[0] == "A", fruit))
The filter() Function
Similar to map()
, filter()
takes a function
object and an iterable and creates a new list.
As the name suggests, filter()
forms a new list that contains only elements that satisfy a certain condition, i.e. the function
we passed returns True
.
The syntax is:
filter(function, iterable(s))
Using the previous example, we can see that the new list will only contain elements for which the starts_with_A()
function returns True
:
# Without using lambdas
def starts_with_A(s):
return s[0] == "A"
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(starts_with_A, fruit)
print(list(filter_object))
Running this code will result in a shorter list:
['Apple', 'Apricot']
Or, rewritten using a lambda:
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(lambda s: s[0] == "A", fruit)
print(list(filter_object))
Printing gives us the same output:
['Apple', 'Apricot']
The reduce() Function
reduce()
works differently than map()
and filter()
. It does not return a new list based on the function
and iterable we've passed. Instead, it returns a single value.
Also, in Python 3 reduce()
isn't a built-in function anymore, and it can be found in the functools
module.
The syntax is:
reduce(function, sequence[, initial])
reduce()
works by calling the function
we passed for the first two items in the sequence. The result returned by the function
is used in another call to function
alongside with the next (third in this case), element.
This process repeats until we've gone through all the elements in the sequence.
The optional argument initial
is used, when present, at the beginning of this "loop" with the first element in the first call to function
. In a way, the initial
element is the 0th element, before the first one, when provided.
reduce()
is a bit harder to understand than map()
and filter()
, so let's look at a step by step example:
-
We start with a list
[2, 4, 7, 3]
and pass theadd(x, y)
function toreduce()
alongside this list, without aninitial
value -
reduce()
callsadd(2, 4)
, andadd()
returns6
-
reduce()
callsadd(6, 7)
(result of the previous call toadd()
and the next element in the list as parameters), andadd()
returns13
-
reduce()
callsadd(13, 3)
, andadd()
returns16
-
Since no more elements are left in the sequence,
reduce()
returns16
The only difference, if we had given an initial
value would have been an additional step - 1.5. where reduce()
would call add(initial, 2)
and use that return value in step 2.
Let's go ahead and use the reduce()
function:
from functools import reduce
def add(x, y):
return x + y
list = [2, 4, 7, 3]
print(reduce(add, list))
Running this code would yield:
16
Again, this could be written using lambdas:
from functools import reduce
list = [2, 4, 7, 3]
print(reduce(lambda x, y: x + y, list))
print("With an initial value: " + str(reduce(lambda x, y: x + y, list, 10)))
And the code would result in:
16
With an initial value: 26
Conclusion
As mentioned previously, these functions are convenience functions. They are there so you can avoid writing more cumbersome code, but avoid using both them and lambda expressions too much.
Don't force these tools because "you can", as it can often lead to illegible code that's hard to maintain. Use them only when it's absolutely clear what's going on as soon as you look at the function or lambda expression.
If you catch yourself struggling to fit the necessary logic into one map()
function, or one lambda expression, it's much better to just write a slightly longer for-loop/defined method and avoid unnecessary confusion later.