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


当前回答

通常的做法是将这类信息放在文件头中,并让pysam允许您访问这些信息。我不知道格式,但是你检查过API了吗?

正如其他人所说,你不能从迭代器中知道长度。

其他回答

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

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

不,任何方法都需要解析每个结果。你可以这样做

iter_length = len(list(iterable))

但是在无限迭代器上运行它当然不会返回。它还将消耗迭代器,如果你想使用内容,它将需要重置。

告诉我们你想要解决的真正问题可能会帮助我们找到更好的方法来实现你的实际目标。

编辑:使用list()会将整个可迭代对象一次性读入内存,这可能是不可取的。另一种方法是做

sum(1 for _ in iterable)

正如另一个人发布的那样。这样可以避免把它保存在记忆中。

这段代码应该工作:

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

关于你最初的问题,答案仍然是,在Python中通常没有办法知道迭代器的长度。

Given that you question is motivated by an application of the pysam library, I can give a more specific answer: I'm a contributer to PySAM and the definitive answer is that SAM/BAM files do not provide an exact count of aligned reads. Nor is this information easily available from a BAM index file. The best one can do is to estimate the approximate number of alignments by using the location of the file pointer after reading a number of alignments and extrapolating based on the total size of the file. This is enough to implement a progress bar, but not a method of counting alignments in constant time.