在Python中,一般情况下有没有一种高效的方法来知道迭代器中有多少元素,而不需要遍历每个元素并计数?
以下代码应该可以正常工作:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
虽然它要遍历每个项并计数,但这是最快的方法。
当迭代器没有任何项时,它也可以工作:
>>> sum(1 for _ in range(0))
0
当然,如果输入是无限的,它将永远运行,所以请记住迭代器可以是无限的:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
此外,请注意 这样做会耗尽迭代器,后续尝试使用它将看到没有元素的情况。这是 Python 迭代器设计的不可避免的后果。如果您想保留这些元素,则需要将它们存储在列表或其他数据结构中。
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
iterator
的长度在通过迭代器迭代之前是未知的。
def gen(): yield random.randint(0, 1)
是无限的,因此您将永远无法通过迭代来找到其长度。 - tgraynumIters = 0; while iterator: numIters +=1
? - Mike Williamsoniter_length = len(list(iterable))
但在无限迭代器上运行它显然永远不会返回。它还将消耗该迭代器,并且如果您想使用其内容,则需要重置它。
告诉我们你试图解决的实际问题可能有助于我们找到更好的方法来完成你的实际目标。
编辑:使用list()
将一次性将整个可迭代对象读入内存,这可能是不希望的。另一种方法是执行
sum(1 for _ in iterable)
正如另一个人发布的那样。这将避免将其保留在内存中。
len(list(iterable))
,它会将所有数据加载到内存中。
您可以使用:reduce(lambda x, _: x+1, iterable, 0)
。编辑:Zonda333 的代码也很好,使用 sum 函数。 - Tomasz Wysockifunctools.reduce
中。 - Daenythimport itertools
from collections import deque
def count_iter_items(iterable):
"""
Consume an iterable not reading it into memory; return the number of items.
"""
counter = itertools.count()
deque(itertools.izip(iterable, counter), maxlen=0) # (consume at C speed)
return next(counter)
(对于Python 3.x,请使用zip
替换itertools.izip
)。
zip
函数的参数顺序很重要,如果你传递 zip(counter, iterable)
,实际上会得到比可迭代对象数量多 1 的结果! - Kye W Shi有点像。你可以检查__length_hint__
方法,但要注意(至少到Python 3.4为止,正如gsnedders所指出的),它是一个未记录的实现细节(线程中的以下消息),它可能会消失,或者召唤鼻涕鬼。
否则没有。迭代器只是一种仅公开next()
方法的对象。您可以根据需要调用它多次,它们可能会最终引发StopIteration
异常。幸运的是,这种行为大部分时间对编码人员来说是透明的。 :)
一个快速基准测试:
import collections
import itertools
def count_iter_items(iterable):
counter = itertools.count()
collections.deque(itertools.izip(iterable, counter), maxlen=0)
return next(counter)
def count_lencheck(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
def count_sum(iterable):
return sum(1 for _ in iterable)
iter = lambda y: (x for x in xrange(y))
%timeit count_iter_items(iter(1000))
%timeit count_lencheck(iter(1000))
%timeit count_sum(iter(1000))
结果:
10000 loops, best of 3: 37.2 µs per loop
10000 loops, best of 3: 47.6 µs per loop
10000 loops, best of 3: 61 µs per loop
也就是说,使用简单的count_iter_items方法是正确的选择。
调整后适用于Python3:
61.9 µs ± 275 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.4 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
82.6 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
因此,对于那些想要了解讨论摘要的人。使用以下方法计算一个长度为5000万的生成器表达式的最终得分:
len(list(gen))
,len([_ for _ in gen])
,sum(1 for _ in gen)
,ilen(gen)
(来自more_itertools),reduce(lambda c, i: c + 1, gen, 0)
,按执行性能(包括内存消耗)排序,将让您惊讶:
```
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('列表,第二', 1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('列表推导式,第二部分', 2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('合计,秒', 3.441088170016883)
d = deque(enumerate(iterable, 1), maxlen=1)
test_ilen.py:10: 0.875 KiB
gen = (i for i in data*1000); t0 = monotonic(); ilen(gen)
('ilen,sec',9.812256851990242)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('reduce, sec', 13.44)
所以,len(list(gen))
是最常用且内存消耗最少的。
len(list(gen))
消耗的内存应该比基于 reduce 的方法更少吗?前者创建了一个涉及内存分配的新 list
,而后者不应该这样做。因此,我期望后者更加内存高效。此外,内存消耗将取决于元素类型。 - normanius我喜欢使用cardinality包,它非常轻量级,并尝试根据可迭代对象使用最快的实现。
用法:
>>> 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
实际的count()
实现如下:
def count(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
count
代码,如果可迭代对象没有 .__len__
属性,它将被消耗。如果它是一个“一次性”对象,比如生成器,在调用 count
后它将为空。 - Stef迭代器只是一个对象,它有一个指针指向缓冲区或流中要读取的下一个对象,就像LinkedList一样,你不知道有多少东西,直到你通过迭代它们。迭代器旨在高效,因为它们只是通过引用告诉你下一个是什么,而不是使用索引(但正如你所看到的,你失去了查看下一个条目数量的能力)。