一般来说,有没有一种有效的方法可以知道Python中的迭代器中有多少个元素,而不用遍历每个元素并计数?


当前回答

有点。你可以检查__length_hint__方法,但要注意(至少在Python 3.4之前,正如gsnedders所指出的那样),它是一个未记录的实现细节(在线程中跟随消息),它很可能消失或召唤鼻子恶魔。

否则,没有。迭代器只是一个只公开next()方法的对象。你可以根据需要多次调用它,它们最终可能引发也可能不会引发StopIteration。幸运的是,大多数时候这种行为对编码器来说是透明的。:)

其他回答

所以,对于那些想知道讨论总结的人。使用以下方法计算5000万长度生成器表达式的最终最高分:

len(列表(创)), Len ([_ for _ in gen]), Sum (1 for _ in gen), Ilen (gen) (from more_itertool), Reduce (c, i: c + 1, gen, 0),

按执行性能排序(包括内存消耗),会让你大吃一惊:

```

1: test_list.py: 8:0.492 KiB

gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))

('list, sec', 1.9684218849870376)

2: test_list_compr.py: 8:0.867 KiB

gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])

('list_compr, sec', 2.5885991149989422)

3: test_sum.py:8: 0.859 KiB

gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()

('sum, sec', 3.441088170016883)

4: more_itertools/more.py:413: 1.266 KiB

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)

5: test_reduce.py:8: 0.859 KiB

gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)

('reduce, sec', 13.436614598002052) ' ' '

因此,len(list(gen))是使用频率最高且占用内存较少的

有点。你可以检查__length_hint__方法,但要注意(至少在Python 3.4之前,正如gsnedders所指出的那样),它是一个未记录的实现细节(在线程中跟随消息),它很可能消失或召唤鼻子恶魔。

否则,没有。迭代器只是一个只公开next()方法的对象。你可以根据需要多次调用它,它们最终可能引发也可能不会引发StopIteration。幸运的是,大多数时候这种行为对编码器来说是透明的。:)

我认为有必要建立一个微观基准来比较这里提到的不同方法的运行时间。

免责声明:我使用simple_benchmark(我编写的库)进行基准测试,还包括iteration_utilities。Count_items(由我编写的第三方库中的函数)。

为了提供更有区别的结果,我做了两个基准测试,一个只包括不构建中间容器的方法,另一个包括以下方法:

from simple_benchmark import BenchmarkBuilder
import more_itertools as mi
import iteration_utilities as iu

b1 = BenchmarkBuilder()
b2 = BenchmarkBuilder()

@b1.add_function()
@b2.add_function()
def summation(it):
    return sum(1 for _ in it)

@b1.add_function()
def len_list(it):
    return len(list(it))

@b1.add_function()
def len_listcomp(it):
    return len([_ for _ in it])

@b1.add_function()
@b2.add_function()
def more_itertools_ilen(it):
    return mi.ilen(it)

@b1.add_function()
@b2.add_function()
def iteration_utilities_count_items(it):
    return iu.count_items(it)

@b1.add_arguments('length')
@b2.add_arguments('length')
def argument_provider():
    for exp in range(2, 18):
        size = 2**exp
        yield size, [0]*size

r1 = b1.run()
r2 = b2.run()

import matplotlib.pyplot as plt

f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=[15, 18])
r1.plot(ax=ax2)
r2.plot(ax=ax1)
plt.savefig('result.png')

结果如下:

它使用log-log-轴,以便可以检查所有范围(小值,大值)。由于这些图是用于定性比较的,因此实际值并不太有趣。一般来说,y轴(垂直)表示时间,x轴(水平)表示输入“可迭代对象”中的元素数量。纵轴上越低表示越快。

上图显示了不使用中间列表的方法。这表明iteration_utilities方法是最快的,其次是more_itertools,最慢的是使用sum(1 for _ in iterator)。

下面的图还包括在中间列表上使用len()的方法,一次使用列表,一次使用列表推导式。使用len(list)的方法在这里是最快的,但与iteration_utilities方法的区别几乎可以忽略不计。使用理解式的方法比直接使用列表的方法慢得多。

总结

这里提到的任何方法都依赖于输入的长度,并且迭代遍历可迭代对象中的每个元素。没有迭代就无法获得长度(即使迭代是隐藏的)。

如果你不想要第三方扩展,那么使用len(list(iterable))绝对是测试过的方法中最快的方法,但是它会生成一个中间列表,可能会使用更多的内存。

如果你不介意额外的包,那么iteration_utilities。Count_items几乎和len(list(…))函数一样快,但不需要额外的内存。

但是需要注意的是,微基准测试使用列表作为输入。基准测试的结果可能不同,这取决于您想要获取的迭代对象的长度。我还用range和一个简单的生成器表达式进行了测试,趋势非常相似,但我不能排除时间不会因输入类型而改变。

一个简单的方法是使用内置函数set()或list():

答:set()在迭代器中没有重复项的情况下(最快的方式)

iter = zip([1,2,3],['a','b','c'])
print(len(set(iter)) # set(iter) = {(1, 'a'), (2, 'b'), (3, 'c')}
Out[45]: 3

or

iter = range(1,10)
print(len(set(iter)) # set(iter) = {1, 2, 3, 4, 5, 6, 7, 8, 9}
Out[47]: 9

B: list()以防迭代器中有重复的项

iter = (1,2,1,2,1,2,1,2)
print(len(list(iter)) # list(iter) = [1, 2, 1, 2, 1, 2, 1, 2]
Out[49]: 8
# compare with set function
print(len(set(iter)) # set(iter) = {1, 2}
Out[51]: 2

迭代器只是一个对象,它有一个指向下一个对象的指针,由某种缓冲区或流读取,它就像一个LinkedList,在那里你不知道你有多少东西,直到你遍历它们。迭代器是高效的,因为它们所做的一切都是通过引用而不是使用索引告诉你下一个是什么(但是正如你所看到的,你失去了查看下一个条目有多少的能力)。