如何在Python 3中使用filter、map和reduce函数

368

这是我习惯于在Python 2中使用的filtermapreduce的方式:

>>> def f(x):
        return x % 2 != 0 and x % 3 != 0
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]

>>> def cube(x):
        return x*x*x
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

>>> def add(x,y):
        return x+y
>>> reduce(add, range(1, 11))
55

然而,所有这些在Python 3中似乎都出现了问题。
>>> filter(f, range(2, 25))
<filter object at 0x0000000002C14908>

>>> map(cube, range(1, 11))
<map object at 0x0000000002C82B70>

>>> reduce(add, range(1, 11))
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    reduce(add, range(1, 11))
NameError: name 'reduce' is not defined

为什么结果不同?我该如何让Python 3代码像Python 2代码一样工作?
另请参阅:reduce()有什么问题?,以了解将reduce放入标准库模块而不是保留为内置函数的具体动机。

1
简而言之,列表不是唯一的数据类型。如果你想要一个列表,就说你想要一个列表。但在大多数情况下,你无论如何都想要其他东西。 - Veky
7个回答

385

您可以查看Python 3.0的新特性。如果从2.x升级到3.x,应该仔细阅读此文档,因为有很多更改。

以下是来自文档的引用。

视图和迭代器代替列表

某些著名的API不再返回列表:

  • [...]
  • map()filter()返回迭代器。如果真的需要一个列表,则快速修复方法是例如list(map(...)),但更好的修复方法通常是使用列表推导式(特别是当原始代码使用lambda时),或者重写代码,使其根本不需要列表。特别棘手的是针对函数副作用调用map();正确的转换是使用常规的for循环(因为创建列表只会浪费资源)。
  • [...]

内置函数

  • [...]
  • 删除了reduce()。如果确实需要,可以使用functools.reduce();然而,99%的情况下,显式的for循环更易读。
  • [...]

33
在所有地方都添加 list(map(...),这怎么能帮助可读性呢?Python 似乎无法处理函数组合器的渐进式 / 流式应用。其他语言中,我可以将一系列操作链接到一个集合上,并使其可读。但在这里,你想要什么 - 一堆嵌套的 in 吗? - WestCoastProjects
11
如果你在一个命令式的环境中工作,那么for循环可能是更可读的选择。但有很好的理由去偏爱函数式的环境,而且从函数式返回到过程式可能会非常丑陋。 - MatrixManAtYrService
3
在“数据流应用程序”中,您是否确信需要添加list调用?我认为“数据流”的意思是“根本不创建列表;在转到下一个元素之前完全处理输入的每个元素”。 - Imperishable Night
6
我仍然无法理解可读性的论点如何导致如此大的变化。如果出于性能原因,我也可以理解... - René Jahn
2
一个“快速修复”(即:hack)是使用list(map...),但请注意,“更好的修复”是改用列表推导式 - 如[Foo(x) for x in mylist]。这不会导致在各处添加list(),从长远来看可能更好。(@javadba FYI) - dmonopoly
显示剩余3条评论

102
mapfilter的功能被故意改变为返回迭代器,并且reduce已从内置函数中移除并放置在functools.reduce中。
因此,对于filtermap,可以使用list()将它们包装起来,以查看与之前相同的结果。
>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> list(filter(f, range(2, 25)))
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> list(map(cube, range(1, 11)))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> import functools
>>> def add(x,y): return x+y
...
>>> functools.reduce(add, range(1, 11))
55
>>>

现在的建议是用生成器表达式或列表推导式来替换使用map和filter。例如:

推荐使用生成器表达式或列表推导式代替map和filter。

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> [i for i in range(2, 25) if f(i)]
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> [cube(i) for i in range(1, 11)]
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>>

人们说,相比reduce函数,for循环99%的时间更易于阅读,但我仍然坚持使用functools.reduce

编辑:99%的数据来自Guido van Rossum编写的Python 3.0新特性页面


6
在列表推导式中,您不需要创建额外的函数。只需使用[i*i*i for i in range(1,11)]即可。 - Xiao
2
你是完全正确的。我在列表推导示例中保留了函数,以使其看起来类似于 filter/map 示例。 - Joshua D. Boyd
5
i的三次方也可以表示为i乘以i乘以i。 - Breezer
5
@Breezer 实际上, i**3会调用i.__pow__(3)i*i*i会调用i.__mul__(i).__mul__(i)(或类似的操作)。对于整数来说这没有关系,但是对于NumPy数字/自定义类可能会产生不同的结果。 - syntonym
@JoshuaD.Boyd 但哪个更快?map 还是 generator - John Strood
3
我注意到每当我们听到“Guido做出决定X”时,痛苦往往是一个可能的结果。这是一个很好的例子:list(list(list(..))),用于执行在Python中已经很冗长的操作。 - WestCoastProjects

12

在其他答案的补充说明中,这似乎是上下文管理器的良好用例,它将重新映射这些函数的名称为返回列表的函数,并引入reduce到全局命名空间。

一个快速实现可能如下所示:

from contextlib import contextmanager    

@contextmanager
def noiters(*funcs):
    if not funcs: 
        funcs = [map, filter, zip] # etc
    from functools import reduce
    globals()[reduce.__name__] = reduce
    for func in funcs:
        globals()[func.__name__] = lambda *ar, func = func, **kwar: list(func(*ar, **kwar))
    try:
        yield
    finally:
        del globals()[reduce.__name__]
        for func in funcs: globals()[func.__name__] = func

使用方法如下:

with noiters(map):
    from operator import add
    print(reduce(add, range(1, 20)))
    print(map(int, ['1', '2']))

打印输出:

190
[1, 2]

这只是我的个人看法 :-)


2
Python 作为一种语言很混乱,但它有非常好的优秀库:numpypandasstatsmodels 等等。我曾经建立过像你在这里展示的便利库,以减轻原生语言的痛苦,但我已经失去了动力,尽量不要偏离 data.frame / datatablexarray。但是,对于你的尝试表示赞赏。 - WestCoastProjects

10

自 Python3 的内置函数中删除了 reduce 方法后,请不要忘记在您的代码中导入 functools。请参考下面的代码片段。

import functools
my_list = [10,15,20,25,35]
sum_numbers = functools.reduce(lambda x ,y : x+y , my_list)
print(sum_numbers)

5

map、filter和reduce的优点之一是,当你将它们"链接"在一起完成某些复杂任务时,它们变得非常易读。然而,内置语法不易读,并且全部"倒置"。因此,我建议使用PyFunctional包(https://pypi.org/project/PyFunctional/)。 下面是两者的比较:

flight_destinations_dict = {'NY': {'London', 'Rome'}, 'Berlin': {'NY'}}

PyFunctional版本

非常易读的语法。你可以说:

"我有一个飞行目的地序列。其中我想要得到字典值中城市的字典键。最后,在这个过程中过滤掉我创建的空列表。"

from functional import seq  # PyFunctional package to allow easier syntax

def find_return_flights_PYFUNCTIONAL_SYNTAX(city, flight_destinations_dict):
    return seq(flight_destinations_dict.items()) \
        .map(lambda x: x[0] if city in x[1] else []) \
        .filter(lambda x: x != []) \

Python默认版本

完全反了。你需要说:

"好的,有一个列表。我想要从中筛选出空列表。为什么?因为我首先得到了字典键,如果城市在字典值中,然后我用这个列表去做这件事。哦,这个列表是 flight_destinations_dict."

def find_return_flights_DEFAULT_SYNTAX(city, flight_destinations_dict):
    return list(
        filter(lambda x: x != [],
               map(lambda x: x[0] if city in x[1] else [], flight_destinations_dict.items())
               )
    )

尝试类似以下代码是否可行:def find_return_flights(city): return [key for key, val in flight_destinations_dict.items() if city in val] - dslack
可能会起作用,但严格来说这不是函数式编程。 - Daniel

1
from functools import reduce

def f(x):
    return x % 2 != 0 and x % 3 != 0

print(*filter(f, range(2, 25)))
#[5, 7, 11, 13, 17, 19, 23]

def cube(x):
    return x**3
print(*map(cube, range(1, 11)))
#[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

def add(x,y):
    return x+y

reduce(add, range(1, 11))
#55

它可以直接使用。要获取映射的输出,请使用 * 或列表


0

以下是Filter、Map和Reduce函数的示例。

numbers = [10,11,12,22,34,43,54,34,67,87,88,98,99,87,44,66]

//Filter

oddNumbers = list(filter(lambda x: x%2 != 0, numbers))

print(oddNumbers)

//Map

multiplyOf2 = list(map(lambda x: x*2, numbers))

print(multiplyOf2)

//Reduce

由于Reduce函数不常用,Python 3中已将其从内置函数中移除。但它仍然可在functools模块中使用:

from functools import reduce

sumOfNumbers = reduce(lambda x,y: x+y, numbers)

print(sumOfNumbers)


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