The map(), filter(), and reduce() functions are three built-in functions that are most often used in conjunction with lambda functions. They are commonly used in functional style Python programming because they allow us to declare data transformations of any complexity, while simultaneously avoiding side-effects. In Python 2, all three functions were available as default built-in functions that did not require additional imports. In Python 3, the reduce() function was moved to the functools module, so it requires additional imports.
map(fun, iterable, ...) applies the func function argument to every item of iterable. You can pass more iterables to the map() function. If you do so, map() will consume elements from each iterable simultaneously. The func function will receive as many items as there is iterables on every map step. If iterables are of different sizes, map() will stop until the shortest one is exhausted. It is worth remembering that map() does not evaluate the whole result at once, but returns an iterator so that every result item can be evaluated only when it is necessary.
The following is an example of map() being used to calculate the squares of the first 10 positive integers, including 0:
>>> map(lambda x: x**2, range(10))
<map object at 0x10ea09cf8>
>>> list(map(lambda x: x**2, range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
The following is an example of the map() function being used over multiple iterables of different sizes:
>>> list(map(print, range(5), range(4), range(5)))
0 0 0
1 1 1
2 2 2
3 3 3
filter(function, iterable) works similarly to map() by evaluating input elements one by one. Unlike map(), the filter() function does not transform input elements into new values, but allows us to filter out those input values that meet the predicate defined by the function argument. The following are examples of the filter() functions usage:
>>> evens = filter(lambda number: number % 2 == 0, range(10))
>>> odds = filter(lambda number: number % 2 == 1, range(10))
>>> print(f"Even numbers in range from 0 to 9 are: {list(evens)}")
Even numbers in range from 0 to 9 are: [0, 2, 4, 6, 8]
>>> print(f"Odd numbers in range from 0 to 9 are: {list(odds)}")
Odd numbers in range from 0 to 9 are: [1, 3, 5, 7, 9]
>>> animals = ["giraffe", "snake", "lion", "squirrel"]
>>> animals_with_s = filter(lambda animal: 's' in animal, animals)
>>> print(f"Animals with letter 's' are: {list(animals_with_s)}")
Animals with letter 's' are: ['snake', 'squirrel']
The reduce(function, iterable) works completely opposite to map(). Instead of taking items of iterable and mapping them to the function return values in a one-by-one fashion, it cumulatively performs operations specified by function to all iterable items. Let's consider following the example of reduce() calls being used to sum values of elements contained in various iterable objects:
>>> from functools import reduce
>>> reduce(lambda a, b: a + b, [2, 2])
4
>>> reduce(lambda a, b: a + b, [2, 2, 2])
6
>>> reduce(lambda a, b: a + b, range(100))
4950
One interesting aspect of map() and filter() is that they can work on infinite sequences. Of course, evaluating infinite sequence to a list type or trying to ordinarily loop over such a sequence will result in program that does not ever end. However the return values of map() and filter() are iterators, and we already learned in this chapter that we can obtain new values from iterators using the next() function. The common range() function we have used in previous examples unfortunately requires finite input value, but the itertools module provides a useful count() function that allows you to count from a specific number in any direction ad infinitum. The following example shows how all these functions can be used together to generate an infinite sequence in a declarative way:
>>> from itertools import count
>>> sequence = filter(
... # We want to accept only values divisible by 3
... # that are not divisible by 2
... lambda square: square % 3 == 0 and square % 2 == 1,
... map(
... # and all numbers must be squares
... lambda number: number ** 2,
... # and we count towards infinity
... count()
... )
... )
>>> next(sequence)
9
>>> next(sequence)
81
>>> next(sequence)
225
>>> next(sequence)
441
Unlike the map() and filter() functions, the reduce() function needs to evaluate all input items in order to return its value, as it does not yield intermediary results. This means that it cannot be used on infinite sequences.
Let's take a look at partial objects and partial() functions.