pythonic style for functional programming

Viewed 319

I don't have much experience with Python. I'm trying to code in a functional style like I'm used to from Java and JavaScript e.g.

var result = getHeroes('Jedi')
  .map(hero => { hero: hero, movies: getMovies(hero) })
  .filter(x => x.movies.contains('A New Hope'));

I'm trying to do something similar in Python but I can't get the same chaining style. I had to break it down to two statements, which I don't like:

tmp = ((hero, get_movies(hero)) for hero in get_heroes('jedi'))
result = ((hero, movies) for (hero, movies) in tmp if movies.contains('A New Hope')

I have two questions:

  1. Is there a way in Python to approach the first style?
  2. What is the idiomatic way of doing this in Python?

Thank you.

5 Answers

As someone who adores functional programming, don't write in functional style in Python.

This hard and fast rule is a little ham-handed, and there are certainly ways to do what you're trying to do using typical functional tools like map, filter, and reduce (called functools.reduce in Python), but it's likely that your functional code will look uglier than sin, in which case there's no reason to prefer it over something imperative and pretty.

result = []
for hero in get_heros("Jedi"):
    movies = get_movies(hero)
    for movie in movies:
        if "A New Hope" in movies:
            result.append((hero, movies))

This could be done with a list comprehension, but is probably less readable.

result = [(hero, movies) for hero in get_heros("Jedi")
          for movies in [get_movies(hero)] if "A New Hope" in movies]

Generator expressions are the Pythonic approach, but a functional solution is possible via a combination of map and filter:

mapper = map(lambda x: (x, get_movies(x)), get_heroes('jedi'))
result = filter(lambda x: x[1].contains('A New Hope'), mapper)

IMO they way of doing that in a functional style in python (not pythonic actually), using map and filter:

result = filter (
    lambda x: x[1].contains('A New Hope'),
    map(
        lambda x: (hero, get_movies(hero)),
        get_heroes('jedi')
    )
)

The pythonic way (not very functional) would be to use a generator expresion:

result = ((hero, get_movies(hero)) for hero in get_heroes("jedi") if "A new hope" in get_movies(hero))

If you are willing to use third-party libraries, I would suggest fn.py with its syntactic sugar for compositions

from fn import F

result = (
    F(map, lambda hero: dict(hero=hero, movies=getMovies(hero))) >>
    (filter, lambda x: 'A New Hope' in x['movies']) >> 
    list
)(getHeroes('Jedi'))

You can remove the last element in the composition, if you don't want a list, though stateful iterators/generators are not very functional. F-objects wrap callables and make partial application and composition easier. A chain of F-expressions is a new function that can be used multiple times. This is closer to functional programming in the classical sense: programs are compositions:

program = (
    F(map, lambda hero: dict(hero=hero, movies=getMovies(hero))) >>
    (filter, lambda x: 'A New Hope' in x['movies']) >> 
    list
)

result = program(getHeroes('Jedi'))
# or even
result = (F(getHeroes) >> program)('Jedi')

I'm surprised nobody has suggested something like the following, using pytoolz's pipe:

from toolz import pipe

result = pipe(
    get_heroes("Jedi"),
    lambda hs: [{"hero": h, "movies": get_movies(h)} for h in hs],
    lambda xs: [x for x in xs if "A New Hope" in x["movies"]],
)
Related