计算生成器/迭代器中项目数量的最简单方法是什么?

如果我想要一个迭代中的项目数量,而不关心这些元素本身,那么将会是什么样的pythonic方式呢? 现在,我会定义

def ilen(it): return sum(itertools.imap(lambda _: 1, it)) # or just map in Python 3 

但我知道lambda已经接近被认为是有害的了,而lambda _: 1肯定不是很漂亮。

(用法是计算匹配正则expression式的文本文件中的行数,即grep -c 。)

通常的方法是

 sum(1 for i in it) 

当迭代器可能很长时(而且在迭代器很短的时候速度不是很慢),同时保持固定的内存开销行为(不像len(list(it)) )以避免交换大型input的抖动和重新分配开销:

 # 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 def ilen(it): # Make a stateful counting iterator cnt = count() # zip it with the input iterator, then drain until input exhausted at C level deque(zip(it, cnt), 0) # 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代码循环( dequecountzip全部用C实现); 避免每个循环的字节码执行通常是CPython性能的关键。

想出比较性能的公平testing用例是非常困难的(使用__length_hint__ list作弊不太可能用于任意input迭代器,不提供__length_hint__ itertools函数通常具有特殊的操作模式,在下一个值被请求之前释放每个循环上返回的释放,其中maxlen=0 deque将会执行)。 我使用的testing用例是创build一个生成器函数,它将接受一个input并返回一个C级别的生成器,该生成器缺乏特殊的itertools返回容器优化或__length_hint__ ,使用Python 3.3的yield from

 def no_opt_iter(it): yield from it 

然后使用ipython %timeit magic(用不同的常量代替100):

 >>> %%timeit -r5 fakeinput = (0,) * 100 ... ilen(no_opt_iter(fakeinput)) 

当input不够大, len(list(it))会导致内存问题时,在运行Python 3.5 x64的Linux机器上,我的解决scheme比def ilen(it): return len(list(it))花费的时间大约多了50% def ilen(it): return len(list(it)) ,而不pipeinput的长度。

对于最小的input,调用deque / zip / count / next的设置成本意味着它比def ilen(it): sum(1 for x in it)花费的时间要长无限多def ilen(it): sum(1 for x in it) (我的机器上的约200 ns更多长度为0的input,比简单sum方法增加了33%),但是对于较长的input,其每个附加​​元素的运行时间约为一半; 对于长度为5的input,成本是相等的,而在长度为50-100的范围内,初始开销与实际工作相比是不明显的; sum方法大约需要两倍的时间。

基本上,如果内存使用问题或input不具有有限的大小,你关心的速度不仅仅是简洁,使用这个解决scheme。 如果input是有限和小的, len(list(it))可能是最好的,如果它们是无界的,但简单/简洁计数,则使用sum(1 for x in it)

一个简短的方法是:

 def ilen(it): return len(list(it)) 

请注意,如果您生成了很多元素(比方说数以万计或更多),那么将它们放入列表中可能会成为性能问题。 然而,这是对大多数情况下性能不起作用的想法的简单expression。

我喜欢这个基数包,它是非常轻量级的,并尝试使用可用的最快实现取决于迭代。

用法:

 >>> 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 

more_itertools是实现了一个ilen工具的第三方库。 pip install more_itertools

 import more_itertools as mit mit.ilen(x for x in range(10)) # 10