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


当前回答

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

其他回答

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

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

假设,您希望在不遍历的情况下计算项的数量,这样迭代器就不会耗尽,稍后可以再次使用它。这是可能的复制或深度复制

import copy

def get_iter_len(iterator):
    return sum(1 for _ in copy.copy(iterator))

###############################################

iterator = range(0, 10)
print(get_iter_len(iterator))

if len(tuple(iterator)) > 1:
    print("Finding the length did not exhaust the iterator!")
else:
    print("oh no! it's all gone")

输出是“查找长度没有耗尽迭代器!”

可选的(并且不明智的),你可以像下面这样为内置的len函数添加阴影:

import copy

def len(obj, *, len=len):
    try:
        if hasattr(obj, "__len__"):
            r = len(obj)
        elif hasattr(obj, "__next__"):
            r = sum(1 for _ in copy.copy(obj))
        else:
            r = len(obj)
    finally:
        pass
    return r

一个简单的基准:

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)

这在理论上是不可能的:事实上,这就是“停止问题”。

证明

相反,假设可以使用函数len(g)来确定任何生成器g的长度(或无限长度)。

对于任何程序P,现在让我们将P转换为生成器g(P): 对于P中的每个返回点或出口点,产生一个值而不是返回它。

如果len(g(P)) ==无穷大,P不会停止。

这解决了暂停问题,这是不可能的,见维基百科。矛盾。


因此,如果不对泛型生成器进行迭代(==实际运行整个程序),就不可能对其元素进行计数。

更具体地说,考虑

def g():
    while True:
        yield "more?"

长度是无限的。这样的发生器有无穷多个。

所以,对于那些想知道讨论总结的人。使用以下方法计算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))是使用频率最高且占用内存较少的