与 `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 forfilter
将filterfalse
称为“互补函数”,所以我想它是为了方便起见。
你可以使用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<=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