与 `filter(not function, iter)` 相比,使用 `itertools.filterfalse()` 有啥效率优势吗?

Posted

技术标签:

【中文标题】与 `filter(not function, iter)` 相比,使用 `itertools.filterfalse()` 有啥效率优势吗?【英文标题】:Are there any efficiency benefits to using `itertools.filterfalse()` as opposed to `filter(not function, iter)`?与 `filter(not function, iter)` 相比,使用 `itertools.filterfalse()` 有什么效率优势吗? 【发布时间】:2020-07-17 20:36:38 【问题描述】:

如果我想使用filter 函数在range(10) 中查找所有大于5 的数字,我可以这样做:

result = filter(lambda x: x>5, range(10))

但是,itertools 模块具有 filterfalse 功能,据我所知,基本上可以

result = filter(lambda x: not x>5, range(10))

我错了吗?拥有这样的功能有什么好处吗?与传统切片相比,islice 等其他函数有一些小的优势,所以我认为filterfalse 也有一个小众用途。

【问题讨论】:

The documentation for filterfilterfalse 称为“互补函数”,所以我想它是为了方便起见。 你可以使用timeit.Timer自己测试 我认为filterfalse 主要用于当您不编写自己的函数时,您可以反转谁的逻辑,而是当您使用没有倒置版本。例如,str.isalpha。虽然你可以用lambda s: not s.isalpha() 包装它,但使用filterfalse 可能会快一点。 【参考方案1】:

我认为 itertools 模块主要提供便利功能。它们允许您快速编写(高效)代码。例如,假设您有一些函数来测试某个属性,例如测试一个对象是否是一个超过 3 个字符的字符串:

def is_long_string(x):
    return isinstance(x, str) and len(x) > 3

现在,如果您想从对象列表中过滤所有此类字符串,您只需编写:

input_objects = [1, 2, 3.14, 'foo', 'bar', 'qux', 'xyzzy']
long_strings = list(filter(is_long_string, input_objects))

但是,如果您想拥有所有 other 对象,则必须编写 is_not_long_string() 函数,或使用 lambda 函数:

not_long_strings = list(filter(lambda x: not is_long_string(x), input_objects))

使用 itertools 可以更轻松地编写:

not_long_strings = list(itertools.filterfalse(is_long_string, input_objects))

filterfalse() 函数只是提供了内置 filter() 函数的“反向”操作,即 Python 语言本身缺少的东西。

您是想使用第一种还是第二种形式(lambda 或 itertools)是个人喜好问题。

在您的示例中,您已经在使用 lambda 函数,因此在这种情况下使用 itertools 不会提供更多的可读性或便利性。

注意:就像 Andrej 在他的回答中已经证明的那样,性能并不是使用 itertools 的主要原因(重写谓词函数可能会带来更好的性能)。

【讨论】:

【参考方案2】:

使用timeit 模块的快速基准测试表明,使用filter()itertools.filterfalse() 具有大致相同的性能。列表理解肯定更快:

from timeit import timeit
from itertools import filterfalse

N = 10_000

def f1():
    return list(filter(lambda x: x>5, range(N)))

def f2():
    return list(filterfalse(lambda x: not x>5, range(N)))

def f3():
    return [i for i in range(N) if i > 5]


t1 = timeit(lambda: f1(), number=10)
t2 = timeit(lambda: f2(), number=10)
t3 = timeit(lambda: f3(), number=10)

print(t1)
print(t2)
print(t3)

在我的电脑上打印(AMD 2400G,Python 3.8):

0.018814088001818163
0.01380085300115752
0.007684074000280816

编辑:从filterfalse() 中删除not

from timeit import timeit
from itertools import filterfalse

N = 10_000

def f1():
    return list(filter(lambda x: x>5, range(N)))

def f2():
    return list(filterfalse(lambda x: x<=5, range(N)))

def f3():
    return [i for i in range(N) if i > 5]


t1 = timeit(lambda: f1(), number=10)
t2 = timeit(lambda: f2(), number=10)
t3 = timeit(lambda: f3(), number=10)

print(t1)
print(t2)
print(t3)

打印:

0.01010196800052654
0.01007204300549347
0.005998152002575807

编辑:使用 Perfplot 进行基准测试:

import perfplot
from itertools import filterfalse


def f1_filter(r):
    return list(filter(lambda x: x>5, r))

def f2_filterfalse(r):
    return list(filterfalse(lambda x: x<=5, r))

def f3_comprehension(r):
    return [i for i in r if i > 5]

def setup(n):
    return range(n)

perfplot.show(
    setup=setup,
    kernels=[f1_filter, f2_filterfalse, f3_comprehension],
    labels=['filter', 'filterfalse', 'comprehension'],
    n_range=[10**i for i in range(1, 7)],
    xlabel='N',
    logx=True,
    logy=True)

演出:

【讨论】:

该输出与我看到的完全不同,我怀疑您复制了错误的输出。 filterfalse beats filter 在你的输出中以一个舒适的余量,尽管测试被堆叠在它上面,额外的not 被放置在filterfalse 测试中。 @user2357112supportsMonica 我从filterfalse() 中删除了not 并将其更改为x&lt;=5。尽管如此,结果还是 +/- 相同(请参阅我的编辑)。你用什么CPU? PS:不要担心否决票,这就是 SO :) 不知道是什么CPU。无论ideone使用什么。 在您的原始输出中,filterfalse 版本在某种程度上花费了filter 版本的大约 73% 的时间,尽管它做了更多的工作。 (如果您认为时间很接近,那么在进行比较时,您可能会混淆 3 和 8。) 您可能希望使用用 C 实现的函数或方法进行测试,而不是使用用 Python 编写的函数,后者几乎总是会更慢。你可以试试(5).__lt__,不过我不确定方法绑定是否会减慢速度。

以上是关于与 `filter(not function, iter)` 相比,使用 `itertools.filterfalse()` 有啥效率优势吗?的主要内容,如果未能解决你的问题,请参考以下文章

将类组件转换为功能组件 TypeError: users.filter is not a function

Uncaught (in promise) TypeError: $data.tableData.filter is not a function

jQuery中filter(),not(),split()的用法

echarts i.getotheraxis is not a function 需要导入哪个js

Uncaught TypeError: jQuery.i18n.browserLang is not a function

$(...)[0].attr is not a function问题