5

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.

jpp
  • 159,742
  • 34
  • 281
  • 339
Nikolaos Georgiou
  • 2,792
  • 1
  • 26
  • 32
  • 1
    [Why isn't Python very good for functional programming?](https://stackoverflow.com/questions/1017621/why-isnt-python-very-good-for-functional-programming) – Duck Dodgers Jan 04 '19 at 09:47
  • 4
    That javascript doesn´t look very functional actually. – Netwave Jan 04 '19 at 09:47
  • well, you can do functional, but chances are your collaborators are going to hate you for that mostly (: if you also work with a functional person, go for it by all means (: – Jeffrey04 Jan 04 '19 at 09:55
  • 2
    @JoeyMallone neither are JavaScript/Java, but the OP wants to write that way – Eli Korvigo Jan 04 '19 at 09:55

5 Answers5

5

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]
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
  • 2
    Your nested `for`-loop example is anything, but Pythonic. What is wrong with generator expressions and comprehensions? – Eli Korvigo Jan 04 '19 at 09:54
  • 1
    @EliKorvigo nothing when those expressions can be concise, but the sort of nested comprehension needed for this logic is cumbersome. Actually my logic is wrong -- he wants `(hero, movies) if "A New Hope" in movies`. – Adam Smith Jan 04 '19 at 09:56
  • I agree with @AdamSmith here: a nested list comprehension gains nothing in legibility over the explicit nested loops – xnx Jan 04 '19 at 09:58
  • @AdamSmith I think the second way is what I wanted, thank you – Nikolaos Georgiou Jan 04 '19 at 10:02
  • @AdamSmith I personnaly would have used a list comp within the for loop, ie `for hero in get_heroes("Jedi"): results.extend([movie for movie in get_movies(hero) if "A New Hope" in movies])` – bruno desthuilliers Jan 04 '19 at 10:03
  • @brunodesthuilliers sure: mix and match as appropriate :) – Adam Smith Jan 04 '19 at 10:09
  • @AdamSmith I totally agree with you, but darn does rxpy make it hard. – Jared Smith Jan 04 '19 at 11:08
3

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)
jpp
  • 159,742
  • 34
  • 281
  • 339
1

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))
Netwave
  • 40,134
  • 6
  • 50
  • 93
  • 1
    generators and generator expressions (including list comprehensions etc) actually ARE typical of functional programming - FWIW, list comprehensions come from Haskell. – bruno desthuilliers Jan 04 '19 at 09:57
1

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')
Eli Korvigo
  • 10,265
  • 6
  • 47
  • 73
  • This is a sincere approach to what could be pythonic and functional actually. I think it would "look" better if you name the function you are building and then apply it separately to the data. – Netwave Jan 04 '19 at 10:08
0

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"]],
)