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.

4
  • 1
    Why isn't Python very good for functional programming? Commented Jan 4, 2019 at 9:47
  • 4
    That javascript doesn´t look very functional actually. Commented Jan 4, 2019 at 9: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 (: Commented Jan 4, 2019 at 9:55
  • 2
    @JoeyMallone neither are JavaScript/Java, but the OP wants to write that way Commented Jan 4, 2019 at 9:55

5 Answers 5

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]
Sign up to request clarification or add additional context in comments.

7 Comments

Your nested for-loop example is anything, but Pythonic. What is wrong with generator expressions and comprehensions?
@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.
I agree with @AdamSmith here: a nested list comprehension gains nothing in legibility over the explicit nested loops
@AdamSmith I think the second way is what I wanted, thank you
@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])
|
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)

Comments

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))

1 Comment

generators and generator expressions (including list comprehensions etc) actually ARE typical of functional programming - FWIW, list comprehensions come from Haskell.
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')

1 Comment

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.