Pythonic风格的函数式编程

5

我对Python没有太多经验。我正在尝试像在Java和JavaScript中一样以函数式编程的方式编码。

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

我正在尝试在Python中做类似的事情,但我无法得到相同的链式样式。我不喜欢把它分解成两个语句:
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')

我有两个问题:

  1. 在Python中有没有一种方法可以接近第一个样式?
  2. 在Python中完成这个任务的惯用方式是什么?

谢谢。


1
为什么Python不太适合函数式编程? - Duck Dodgers
4
这段 JavaScript 看起来实际上不是很实用。 - Netwave
好的,你可以使用函数式编程,但很可能你的合作者会因此而讨厌你(: 如果你也和一个函数式编程的人一起工作,那就尽管去做吧(: - Jeffrey04
2
@JoeyMallone,JavaScript/Java也不是,但OP想要那种写法。 - Eli Korvigo
5个回答

5
作为一个热爱函数式编程的人,不要在Python中使用函数式风格
这个硬性规定有点生硬,当然可以使用典型的函数式工具,如mapfilterreduce(在Python中称为functools.reduce)来完成你想做的事情,但很可能你的函数式代码看起来会非常丑陋,这时就没有理由优先选择它而不是易于理解的命令式代码了。
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))

这可以使用列表推导式来完成,但可能不太易读。
result = [(hero, movies) for hero in get_heros("Jedi")
          for movies in [get_movies(hero)] if "A New Hope" in movies]

2
你的嵌套 for 循环示例与 Python 风格不符。使用生成器表达式和推导式会更好,你觉得呢? - Eli Korvigo
1
@EliKorvigo 当这些表达式可以简洁时,什么都不需要,但是为了这种逻辑所需的嵌套理解很麻烦。实际上我的逻辑是错误的 - 他想要 (hero, movies) if "A New Hope" in movies - Adam Smith
@AdamSmith 我认为第二种方式是我想要的,谢谢。 - Nikolaos Georgiou
@brunodesthuilliers 没问题:可以根据需要进行混搭 :) - Adam Smith
@AdamSmith 我完全同意你的观点,但是 rxpy 真的让人很难受。 - Jared Smith
显示剩余2条评论

3
生成器表达式是Pythonic的方法,但通过mapfilter的组合也可以实现功能性解决方案。
mapper = map(lambda x: (x, get_movies(x)), get_heroes('jedi'))
result = filter(lambda x: x[1].contains('A New Hope'), mapper)

1

在Python中,以函数式的方式(实际上不是Pythonic的方式)使用mapfilter来实现这一点:

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

Pythonic的方式(不是非常函数式)是使用生成器表达式:

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

1
生成器和生成器表达式(包括列表推导等)实际上是函数式编程的典型特征 - 顺便说一下,列表推导来自 Haskell。 - bruno desthuilliers

1
如果您愿意使用第三方库,我建议使用fn.py,它具有组合的语法糖。
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'))

如果您不想要一个列表,可以删除组合中的最后一个元素,尽管有状态的迭代器/生成器并不是非常函数化。 F对象包装可调用对象,使部分应用和组合更容易。一系列F表达式是一个新函数,可以多次使用。这更接近于经典意义上的函数式编程:程序是组合的:
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')

这是一个真诚的方法,可以使Pythonic和Functional实际上成为可能。我认为如果您命名正在构建的函数,然后将其单独应用于数据,它会“看起来”更好。 - Netwave

0

我很惊讶没有人像下面这样建议,使用pytoolzpipe

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接