部分应用的列表推导式
我正在开始学习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 个回答
你可以通过使用内置的 map
、filter
和 functools.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!']
注意在最后一个例子中,我们只给 map
和 filter
传递了一个参数。这就是柯里化!当我们提供最后一个参数时,函数就会被调用,结果确实是一样的。
为了更清楚地说明发生了什么,我们分别使用 map
和 filter
:
>>> 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]
你可以使用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!']
>>>
这里特别的地方不是列表推导式,而是函数的定义。恰好这个函数返回的是一个列表。所以,只需要在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))