如果我想要迭代中的项目数而不关心元素本身,那么pythonic的方法是什么?现在,我会定义
def ilen(it):
return sum(itertools.imap(lambda _: 1, it)) # or just map in Python 3
但我知道lambda
接近被认为是有害的,而且lambda _: 1
肯定不漂亮。
(这个用例是计算匹配正则表达式的文本文件中的行数,即grep -c
。)
itertools.imap()
在 Python 2 或Python 3 中的调用map()
可以被等效的生成器表达式替换:
sum(1 for dummy in it)
这也使用了惰性生成器,因此它避免了在内存中实现所有迭代器元素的完整列表。
方法比sum(1 for i in it)
当可迭代可能很长时明显更快(当迭代很短时不会显着变慢),同时保持固定的内存开销行为(与 不同len(list(it))
)以避免较大输入的交换抖动和重新分配开销:
# On Python 2 only, get zip that lazily generates results instead of returning list
from future_builtins import zip
from collections import deque
from itertools import count
# Avoid constructing a deque each time, reduces fixed overhead enough
# that this beats the sum solution for all but length 0-1 inputs
consumeall = deque(maxlen=0).extend
def ilen(it):
# Make a stateful counting iterator
cnt = count()
# zip it with the input iterator, then drain until input exhausted at C level
consumeall(zip(it, cnt)) # cnt must be second zip arg to avoid advancing too far
# Since count 0 based, the next value is the count
return next(cnt)
就像len(list(it))
它在 CPython 上用 C 代码执行循环一样(deque
,count
并且zip
都用 C 实现);避免每个循环执行字节码通常是 CPython 性能的关键。
想出公平的测试用例来比较性能是非常困难的(使用不可能用于任意输入迭代的list
作弊,不提供的函数通常具有特殊的操作模式,当每个循环返回的值时工作得更快在请求下一个值之前释放/释放,这会做)。我使用的测试用例是创建一个生成器函数,它接受输入并返回一个缺少特殊返回容器优化的 C 级生成器,或者使用 Python 3.3+ 的:__length_hint__
itertools
__length_hint__
deque
maxlen=0
itertools
__length_hint__
yield from
def no_opt_iter(it):
yield from it
然后使用ipython
%timeit
魔法(用不同的常数代替 100):
>>> %%timeit fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
当输入不够大len(list(it))
而导致内存问题时,在运行 Python 3.9 x64 的 Linux 机器上def ilen(it): return len(list(it))
,无论输入长度如何,我的解决方案所需的时间都比 长约 50%。
对于最小的输入,加载/调用///的设置成本意味着这种consumeall
方式花费的时间比(对于长度为 0 的输入在我的机器上多约 40 ns,比简单方法增加 10%)要长得多,但是通过当您达到长度 2 输入时,成本是相等的,并且在长度 30 左右的某个地方,与实际工作相比,初始开销并不明显;该方法需要大约 50% 的时间。zip
count
next
def ilen(it): sum(1 for _ in it)
sum
sum
基本上,如果内存使用很重要或输入没有限制大小,并且您更关心速度而不是简洁,请使用此解决方案。如果输入是有界且较小的,len(list(it))
可能是最好的,如果它们是无界的,但简单/简洁很重要,你会使用sum(1 for _ in it)
.
一个简短的方法是:
def ilen(it):
return len(list(it))
请注意,如果您要生成大量元素(例如,数万或更多),那么将它们放在列表中可能会成为性能问题。但是,这是对大多数情况下性能无关紧要的想法的简单表达。
more_itertools
是一个实现工具的第三方库ilen
。 pip install more_itertools
import more_itertools as mit
mit.ilen(x for x in range(10))
# 10
len(list(it))
虽然,如果它是一个无限生成器,它可以挂断。
我喜欢这个基数包,它非常轻量级,并尝试根据可迭代使用尽可能快的实现。
用法:
>>> import cardinality
>>> cardinality.count([1, 2, 3])
3
>>> cardinality.count(i for i in range(500))
500
>>> def gen():
... yield 'hello'
... yield 'world'
>>> cardinality.count(gen())
2
这些将是我的选择之一:
print(len([*gen]))
print(len(list(gen)))