How to use filter, map, and reduce in Python 3

320,407

Solution 1

You can read about the changes in What's New In Python 3.0. You should read it thoroughly when you move from 2.x to 3.x since a lot has been changed.

The whole answer here are quotes from the documentation.

Views And Iterators Instead Of Lists

Some well-known APIs no longer return lists:

  • [...]
  • map() and filter() return iterators. If you really need a list, a quick fix is e.g. list(map(...)), but a better fix is often to use a list comprehension (especially when the original code uses lambda), or rewriting the code so it doesn’t need a list at all. Particularly tricky is map() invoked for the side effects of the function; the correct transformation is to use a regular for loop (since creating a list would just be wasteful).
  • [...]

Builtins

  • [...]
  • Removed reduce(). Use functools.reduce() if you really need it; however, 99 percent of the time an explicit for loop is more readable.
  • [...]

Solution 2

The functionality of map and filter was intentionally changed to return iterators, and reduce was removed from being a built-in and placed in functools.reduce.

So, for filter and map, you can wrap them with list() to see the results like you did before.

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> list(filter(f, range(2, 25)))
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> list(map(cube, range(1, 11)))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> import functools
>>> def add(x,y): return x+y
...
>>> functools.reduce(add, range(1, 11))
55
>>>

The recommendation now is that you replace your usage of map and filter with generators expressions or list comprehensions. Example:

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> [i for i in range(2, 25) if f(i)]
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> [cube(i) for i in range(1, 11)]
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>>

They say that for loops are 99 percent of the time easier to read than reduce, but I'd just stick with functools.reduce.

Edit: The 99 percent figure is pulled directly from the What’s New In Python 3.0 page authored by Guido van Rossum.

Solution 3

As an addendum to the other answers, this sounds like a fine use-case for a context manager that will re-map the names of these functions to ones which return a list and introduce reduce in the global namespace.

A quick implementation might look like this:

from contextlib import contextmanager    

@contextmanager
def noiters(*funcs):
    if not funcs: 
        funcs = [map, filter, zip] # etc
    from functools import reduce
    globals()[reduce.__name__] = reduce
    for func in funcs:
        globals()[func.__name__] = lambda *ar, func = func, **kwar: list(func(*ar, **kwar))
    try:
        yield
    finally:
        del globals()[reduce.__name__]
        for func in funcs: globals()[func.__name__] = func

With a usage that looks like this:

with noiters(map):
    from operator import add
    print(reduce(add, range(1, 20)))
    print(map(int, ['1', '2']))

Which prints:

190
[1, 2]

Just my 2 cents :-)

Solution 4

Since the reduce method has been removed from the built in function from Python3, don't forget to import the functools in your code. Please look at the code snippet below.

import functools
my_list = [10,15,20,25,35]
sum_numbers = functools.reduce(lambda x ,y : x+y , my_list)
print(sum_numbers)

Solution 5

One of the advantages of map, filter and reduce is how legible they become when you "chain" them together to do something complex. However, the built-in syntax isn't legible and is all "backwards". So, I suggest using the PyFunctional package (https://pypi.org/project/PyFunctional/). Here's a comparison of the two:

flight_destinations_dict = {'NY': {'London', 'Rome'}, 'Berlin': {'NY'}}

PyFunctional version

Very legible syntax. You can say:

"I have a sequence of flight destinations. Out of which I want to get the dict key if city is in the dict values. Finally, filter out the empty lists I created in the process."

from functional import seq  # PyFunctional package to allow easier syntax

def find_return_flights_PYFUNCTIONAL_SYNTAX(city, flight_destinations_dict):
    return seq(flight_destinations_dict.items()) \
        .map(lambda x: x[0] if city in x[1] else []) \
        .filter(lambda x: x != []) \

Default Python version

It's all backwards. You need to say:

"OK, so, there's a list. I want to filter empty lists out of it. Why? Because I first got the dict key if the city was in the dict values. Oh, the list I'm doing this to is flight_destinations_dict."

def find_return_flights_DEFAULT_SYNTAX(city, flight_destinations_dict):
    return list(
        filter(lambda x: x != [],
               map(lambda x: x[0] if city in x[1] else [], flight_destinations_dict.items())
               )
    )
Share:
320,407
Dick Lucas
Author by

Dick Lucas

Co-Founder of Fullsend, a software solutions shop.

Updated on January 14, 2021

Comments

  • Dick Lucas
    Dick Lucas over 3 years

    filter, map, and reduce work perfectly in Python 2. Here is an example:

    >>> def f(x):
            return x % 2 != 0 and x % 3 != 0
    >>> filter(f, range(2, 25))
    [5, 7, 11, 13, 17, 19, 23]
    
    >>> def cube(x):
            return x*x*x
    >>> map(cube, range(1, 11))
    [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
    
    >>> def add(x,y):
            return x+y
    >>> reduce(add, range(1, 11))
    55
    

    But in Python 3, I receive the following outputs:

    >>> filter(f, range(2, 25))
    <filter object at 0x0000000002C14908>
    
    >>> map(cube, range(1, 11))
    <map object at 0x0000000002C82B70>
    
    >>> reduce(add, range(1, 11))
    Traceback (most recent call last):
      File "<pyshell#8>", line 1, in <module>
        reduce(add, range(1, 11))
    NameError: name 'reduce' is not defined
    

    I would appreciate if someone could explain to me why this is.

    Screenshot of code for further clarity:

    IDLE sessions of Python 2 and 3 side-by-side

  • Xiao
    Xiao almost 10 years
    You do not need to create extra functions in list comprehensions. Just use [i*i*i for i in range(1,11)]
  • Joshua D. Boyd
    Joshua D. Boyd over 9 years
    You are absolutely correct. I kept the function in the list comprehension examples to keep it looking similar to the filter/map examples.
  • Breezer
    Breezer over 8 years
    i**3 is also equivalent of i*i*i
  • syntonym
    syntonym about 8 years
    @Breezer actually i**3 will call i.__pow__(3) and i*i*i i.__mul__(i).__mul__(i) (or something like that). With ints it doesn't matter but with numpy numbers/custom classes it might even produce different results.
  • WestCoastProjects
    WestCoastProjects about 6 years
    Adding list(map(...) everywhere .. how in the world is that helping readability.. python can't seem to handle progressive / streaming application of functional combinators. Other languages I can chain a dozen operations against a collection in a row and it's readable. Here? what do you want - a dozen way nested in ??
  • MatrixManAtYrService
    MatrixManAtYrService almost 6 years
    If you're working in an imperative context, then a for-loop is probably the more readable option. But there are good reasons to prefer a functional context--and breaking from that to go back to procedural can be pretty darn ugly.
  • John Strood
    John Strood over 5 years
    @JoshuaD.Boyd But which is faster? A map or generator?
  • Imperishable Night
    Imperishable Night over 5 years
    @javadba Are you sure in a "streaming application" you need to add the list call at all? I thought the meaning of "streaming" is "no list is created at all; process each element of the input fully before moving on to the next".
  • Imperishable Night
    Imperishable Night over 5 years
    @MatrixManAtYrService If you are sure the python 2 behavior is what you need, you can always just redefine map.
  • WestCoastProjects
    WestCoastProjects over 5 years
    I have noticed that whenever we hear that "Guido made decision X" that pain is a likely outcome. This is a great example: list(list(list(.. ))) to do what was already verbose in python.
  • WestCoastProjects
    WestCoastProjects over 5 years
    python as a language is a mess - but it has v good to excellent libraries: numpy, pandas, statsmodels and friends.. I had been buliding convenience libraries like you show here to reduce the pain of the native language - but have lost the energy and try not to stray far from a data.frame / datatable, or xarray. But kudos for trying..
  • René Jahn
    René Jahn over 5 years
    I still cant grasp how a readability argument leads to such a change. If it was for performance reasons I might understand...
  • Kaos
    Kaos over 4 years
    readability is one thing, but it makes (list, dict, ...)comprehensions unusable. Can't put an ordinary for loop in the middle of a list comprehension, now can I.. ugh.
  • dmonopoly
    dmonopoly over 4 years
    A "quick fix" (read: hack) is to use list(map...) but notice the "better fix" is to use a list comprehension instead - like [Foo(x) for x in mylist]. This doesn't lead to adding list() everywhere and longer term may be better. (@javadba FYI)
  • WestCoastProjects
    WestCoastProjects over 4 years
    @dmonopoly I specifically mentioned applying a dozen transformations and that I do not want to have a 12 way nested list comprehension - which is the only option in standard python afaik
  • dslack
    dslack about 3 years
    Would it also work to try something like: python def find_return_flights(city): return [key for key, val in flight_destinations_dict.items() if city in val]
  • Daniel
    Daniel about 3 years
    It probably would, but that wouldn't be strictly speaking functional programming.