在Python中获取迭代器中的元素数量


Answers:


101

不行,不可能

例:

import random

def gen(n):
    for i in xrange(n):
        if random.randint(0, 1) == 0:
            yield i

iterator = gen(10)

的长度iterator未知,直到您遍历为止。


14
另外,它def gen(): yield random.randint(0, 1)是无限的,所以您将永远无法通过迭代来找到长度。
tgray,2010年

1
因此,为了验证显而易见的事实:获得迭代器“大小”的最佳方法就是简单地计算迭代的次数,对吗?在这种情况下,将是numIters = 0 ; while iterator: numIters +=1
Mike Williamson

有趣,所以这是停顿的问题
赤羽场

230

此代码应工作:

>>> 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迭代器设计不可避免的结果。如果要保留元素,则必须将它们存储在列表或其他内容中。


10
在我看来,这确实完成了OP不想做的事情:遍历迭代器并计数。
亚当·克罗斯兰

36
这是一种节省空间的方法,可迭代计算元素的数量
Lepton上尉,2012年

9
尽管这不是OP想要的,但考虑到他的问题没有答案,该答案避免了实例化列表,并且根据经验,通过常数比上面列出的reduce方法更快。
菲利普·诺德沃​​尔

5
不禁:是_Perl的参考$_吗?:)
Alois Mahdal

17
@AloisMahdal否。在Python中,习惯上将名称_用于您不需要关心其值的虚拟变量。
泰蒙

67

不,任何方法都将要求您解决所有结果。你可以做

iter_length = len(list(iterable))

但是在无限迭代器上运行该函数当然永远不会返回。它还将消耗迭代器,并且如果要使用其内容,则需要将其重置。

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

编辑:使用list()将立即将整个可迭代对象读取到内存中,这可能是不可取的。另一种方法是

sum(1 for _ in iterable)

如另一个人所张贴。这样可以避免将其保存在内存中。


问题是我正在读取具有数百万个条目的“ pysam”文件。Pysam返回一个迭代器。要计算一定数量,我需要知道文件中有多少次读取,但是我不需要读取每个读取...这就是问题所在。

6
我不是pysam用户,但可能正在读取文件“懒惰”。这是有道理的,因为您不想在内存中拥有大文件。因此,如果您一定不知道。对记录进行迭代之前,唯一的方法是创建两个迭代器,并使用第一个迭代器计数元素,然后使用第二个迭代器读取文件。顺便说一句。不使用len(list(iterable))它将所有数据加载到内存。您可以使用:reduce(lambda x, _: x+1, iterable, 0)。编辑:Zonda333代码总和也不错。
Tomasz Wysocki

1
@ user248237:为什么要说您需要知道多少条目可用于计算一定数量?您可以读取固定数量的内容,并在数量少于固定数量的情况下进行管理(使用iterslice确实很简单)。您还必须阅读所有条目吗?
kriss

1
@Tomasz请注意,reduce已过时,它将在Python 3及更高版本中消失。
野鸭

7
@Wilduck:它没有消失,只是搬到了functools.reduce
Daenyth

33

您不能(除非特定迭代器的类型实现了某些特定方法才能实现)。

通常,您只能通过使用迭代器来计数迭代器项目。可能是最有效的方法之一:

import 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,请替换itertools.izipzip)。


3
+1:与的时间比较sum(1 for _ in iterator),速度几乎快一倍。
奥古斯都(Augustomen)

1
准确地说,它通过将每一项读入内存并将其立即丢弃来消耗可迭代项。
Rockallite

这是需要注意的(我忽略),该重要的参数顺序zip事项:如果你通过zip(counter, iterable),你就会得到1以上的迭代次数!
Kye W Shi

非常好的答案。会给它赏金。
Reut Sharabani

18

金田 您可以检查该__length_hint__方法,但要警告(至少gsnedders指出,至少在Python 3.4之前),这是一个未记录的实现细节遵循线程中的消息),很可能消失或召唤鼻恶魔。

否则,不会。迭代器只是一个仅公开next()方法的对象。您可以根据需要多次调用它,它们最终可能会也可能不会出现StopIteration。幸运的是,这种行为在大多数情况下对编码员是透明的。:)


5
PEP 424和Python 3.4开始,情况不再如此。__length_hint__现在已记录在案,但这仅是提示,并不保证准确性。
gsnedders 2014年

12

我喜欢基数软件包,它非常轻巧,并根据可迭代性尝试使用可能的最快实现。

用法:

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

我认为如果使用该函数,您仍然可以遍历迭代器,是吗?
jcollum

12

因此,对于那些想了解该讨论摘要的人。使用以下方法计算长度为5000万的生成器表达式的最终最高分:

  • len(list(gen))
  • len([_ for _ in gen])
  • sum(1 for _ in gen),
  • ilen(gen)(来自more_itertool),
  • reduce(lambda 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))

(“列表,秒”,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))是最频繁且消耗较少的内存


您如何衡量内存消耗?
normanius

您能解释为什么len(list(gen))比基于减少的方法消耗更少的内存吗?前者创建一个list涉及内存分配的新内容,而后者则不涉及。因此,我希望后者可以提高内存效率。同样,内存消耗将取决于元素类型。
normanius

仅供参考:对于python 3.6.8(在MacBookPro上),我可以重现,方法1在运行时方面优于其他方法(我跳过了方法4)。
normanius

len(tuple(iterable))效率更高:Nelson Minar
撰写的

9

迭代器只是一个对象,该对象具有指向要由某种缓冲区或流读取的下一个对象的指针,就像一个LinkedList,在其中迭代之前,您不知道自己拥有多少东西。迭代器之所以具有效率,是因为它们所做的只是告诉您引用之后是什么,而不是使用索引(但是如您所见,您失去了查看下一步有多少项的能力)。


2
迭代器与链表完全不同。从迭代器返回的对象不会指向下一个对象,并且这些对象不会(必需)存储在内存中。相反,它可以根据任何内部逻辑(可以但不一定必须基于存储的列表)一个接一个地生成对象。
汤姆

1
@Tom我以LinkedList为例,主要是因为您只知道下一步的意义(如果有的话),因此您不知道拥有多少。如果我的措辞似乎有些偏离,或者我暗示它们是相同的,我深表歉意。
耶稣拉莫斯

8

关于您的原始问题,答案仍然是,通常没有办法知道Python中迭代器的长度。

鉴于您的问题是由pysam库的应用引起的,我可以给出一个更具体的答案:我是PySAM的贡献者,而最终的答案是SAM / BAM文件未提供对齐读取的确切数目。也无法从BAM索引文件中轻松获得此信息。最好的办法是在读取多个对齐方式并根据文件的总大小外推后,通过使用文件指针的位置来估计对齐的大概数量。这足以实现进度条,但不足以在恒定时间内计数路线。


6

快速基准:

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)

注意:此测试基于python2
normanius

3

有两种方法可以获取计算机上“某物”的长度。

第一种方法是存储计数-这需要接触文件/数据的任何东西来修改它(或仅公开接口的类-但归结为同一件事)。

另一种方法是遍历它并计算它的大小。


0

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

正如其他人所说,您无法从迭代器知道长度。


0

这违反了迭代器的定义,迭代器是指向对象的指针,外加有关如何到达下一个对象的信息。

迭代器不知道在终止之前它将可以迭代多少次。这可能是无限的,所以无限可能是您的答案。


它没有违反任何内容,使用迭代器时应用先验知识也没有错。周围有成千上万的迭代器,您知道元素的数量是有限的。考虑简单地过滤列表,您可以轻松地给出最大长度,只是您实际上并不知道实际上有多少个元素适合您的过滤条件。想要知道匹配元素的数量是一个有效的应用程序,没有违反任何迭代器的神秘概念。
迈克尔(Michael

0

尽管通常不可能执行所要求的操作,但在对项目进行迭代之后,对迭代的项目数进行计数通常仍然有用。为此,您可以使用jaraco.itertools.Counter或类似的名称。这是一个使用Python 3和rwt加载程序包的示例。

$ rwt -q jaraco.itertools -- -q
>>> import jaraco.itertools
>>> items = jaraco.itertools.Counter(range(100))
>>> _ = list(counted)
>>> items.count
100
>>> import random
>>> def gen(n):
...     for i in range(n):
...         if random.randint(0, 1) == 0:
...             yield i
... 
>>> items = jaraco.itertools.Counter(gen(100))
>>> _ = list(counted)
>>> items.count
48

-1
def count_iter(iter):
    sum = 0
    for _ in iter: sum += 1
    return sum

-1

大概是,您希望不迭代地对项目数进行计数,以使迭代器不会耗尽,以后再使用它。可以通过copydeepcopy

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

输出为“Finding the length did not exhaust the iterator!

您可以选择(并且不建议使用)隐藏内置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

1
范围不是迭代器。有一些迭代器类型可以复制,但是其他类型会导致此代码失败并出现TypeError(例如,生成器),并且通过复制的迭代器进行迭代可能会导致副作用发生两次,或者导致代码中的任意破坏,例如,返回一个map迭代器,期望生成的函数调用仅发生一次。
user2357112支持Monica19的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.