部分应用的列表推导式

1 投票
3 回答
878 浏览
提问于 2025-04-18 13:02

我正在开始学习Haskell,发现那里可以把列表推导式变成一个部分应用的函数。换句话说,它会返回一个函数,这个函数接受一个列表,然后对这个列表运行列表推导式。

举个例子:

boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x]

boomBangs can then be called with the actual "xs" argument.

我的问题是:在Python中有没有办法做到这一点?我查了很多地方,但找不到方法,可能我漏掉了什么。

如果能做到这一点,那将非常有价值。

编辑:

似乎大家对我想表达的意思有些困惑。

我希望能够定义列表推导式而不需要立刻给它要处理的列表,可以在稍后再提供。

我的想法是,列表推导式实际上变成了一个接受一个参数的函数,这个参数就是要处理的列表。

比如,我可以这样做:

my_new_func = [x*2 for x in l] # l is **not defined**

然后在代码的某个地方:

my_new_func(range(10)) # Returns the answer

这是一种非常好的函数式编程方式。

第二次编辑:

这里有一种方法可以实现我想要的,但我想知道是否还有更好的方法:

boomBangs = lambda lst: [actual list comprehension]

boomBangs(range(10))

3 个回答

0

你可以通过使用内置的 mapfilterfunctools.partial 函数来实现你想要的风格,但结果可能没有其他人提到的那样清晰或方便。其实,部分应用和柯里化确实是很强大的技巧,可以在 Python 中使用。

你可以在这里了解更多关于部分应用的内容:en.wikipedia.org/wiki/Partial_application

还有柯里化的相关信息:en.wikipedia.org/wiki/Currying

我们可以通过使用 toolz 这个 Python 包来简单演示,这个包里有很多在其他函数式编程语言中常见的实用工具。toolz 还提供了一个方便的 柯里化 命名空间,我将用它来复现你的例子:

>>> # define our primitive functions for clarity
>>> boombang = lambda x: "BOOM!" if x < 10 else "BANG!"
>>> is_odd = lambda x: x % 2 == 1

>>> # given a list, using built-in functions we can do
>>> map(boombang, filter(is_odd, range(20)))
['BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BANG!', 'BANG!', 'BANG!', 'BANG!', 'BANG!']

>>> # if we want to provide the list later, we can do
>>> from toolz.curried import map, filter, compose
>>> boomBangs = compose(map(boombang), filter(is_odd))

>>> # ... Now we have our input list, so let's use it!
>>> list(boomBangs(range(20))
['BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BANG!', 'BANG!', 'BANG!', 'BANG!', 'BANG!']

注意在最后一个例子中,我们只给 mapfilter 传递了一个参数。这就是柯里化!当我们提供最后一个参数时,函数就会被调用,结果确实是一样的。

为了更清楚地说明发生了什么,我们分别使用 mapfilter

>>> from toolz.curried import map, filter
>>> map_boombang = map(boombang)
>>> list(map_boombang([9, 10, 11]))
['BOOM!', 'BANG!', 'BANG!']

>>> filter_odd = filter(is_odd)
>>> list(filter_odd(range(10)))
[1, 3, 5, 7, 9]
1

你可以使用Python的列表推导式:

xs = ["BOOM!" if x < 10 else "BANG!" for x in range(20) if x % 2 == 1]

如果想把这个放进一个函数里,可以这样做:

def boombang(xs):
    return ["BOOM!" if x < 10 else "BANG!" for x in xs if x % 2 == 1]

你也可以使用 lambda 表达式:

>>> boombang = lambda xs: ["BOOM!" if x < 10 else "BANG!" for x in xs if x % 2 == 1]
>>> boombang(range(-10, 20))
['BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BANG!', 'BANG!', 'BANG!', 'BANG!', 'BANG!']
>>> 

列表推导式的工作方式有点特别,它的定义在后面,而语句在前面。在这个列表推导式的开头,我们说 "BOOM!" 如果 x < 10 否则 "BANG!",这可以理解为:

if x < 10:
    xs.append("BOOM!")
else:
    xs.append("BANG!")

在第二部分,我们把 x 定义为从0到20的每一个数字。接下来,我们确保这个循环只会在 x 是奇数的时候完成,这个是通过Python的取余运算来实现的。


>>> xs = ["BOOM!" if x < 10 else "BANG!" for x in range(20) if x % 2 == 1]
>>> xs
['BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BOOM!', 'BANG!', 'BANG!', 'BANG!', 'BANG!', 'BANG!']
>>> 
5

这里特别的地方不是列表推导式,而是函数的定义。恰好这个函数返回的是一个列表。所以,只需要在Python中把它定义成一个普通的函数就可以了。

boomBangs = lambda xs: ["BOOM!" if x < 10 else "BANG!" for x in xs if odd(x)]

def boomBang(xs):
  return ["BOOM!" if x < 10 else "BANG!" for x in xs if odd(x)]

如果你需要它是懒加载的,那就用生成器表达式(genex)来代替。

boomBangs = lambda xs: ("BOOM!" if x < 10 else "BANG!" for x in xs if odd(x))

def boomBang(xs):
  return ("BOOM!" if x < 10 else "BANG!" for x in xs if odd(x))

撰写回答