84

如果我想要迭代中的项目数而不关心元素本身,那么pythonic的方法是什么?现在,我会定义

def ilen(it):
    return sum(itertools.imap(lambda _: 1, it))    # or just map in Python 3

但我知道lambda接近被认为是有害的,而且lambda _: 1肯定不漂亮。

(这个用例是计算匹配正则表达式的文本文件中的行数,即grep -c。)

4

7 回答 7

177

itertools.imap()在 Python 2 或Python 3 中的调用map()可以被等效的生成器表达式替换:

sum(1 for dummy in it)

这也使用了惰性生成器,因此它避免了在内存中实现所有迭代器元素的完整列表。

于 2011-03-21T22:37:14.623 回答
43

方法比sum(1 for i in it)当可迭代可能很长时明显更快(当迭代很短时不会显着变慢),同时保持固定的内存开销行为(与 不同len(list(it)))以避免较大输入的交换抖动和重新分配开销:

# On Python 2 only, get zip that lazily generates results instead of returning list
from future_builtins import zip

from collections import deque
from itertools import count

# Avoid constructing a deque each time, reduces fixed overhead enough
# that this beats the sum solution for all but length 0-1 inputs
consumeall = deque(maxlen=0).extend

def ilen(it):
    # Make a stateful counting iterator
    cnt = count()
    # zip it with the input iterator, then drain until input exhausted at C level
    consumeall(zip(it, cnt)) # cnt must be second zip arg to avoid advancing too far
    # Since count 0 based, the next value is the count
    return next(cnt)

就像len(list(it))它在 CPython 上用 C 代码执行循环一样(dequecount并且zip都用 C 实现);避免每个循环执行字节码通常是 CPython 性能的关键。

想出公平的测试用例来比较性能是非常困难的(使用不可能用于任意输入迭代的list作弊,不提供的函数通常具有特殊的操作模式,当每个循环返回的值时工作得更快在请求下一个值之前释放/释放,这会做)。我使用的测试用例是创建一个生成器函数,它接受输入并返回一个缺少特殊返回容器优化的 C 级生成器,或者使用 Python 3.3+ 的:__length_hint__itertools__length_hint__dequemaxlen=0itertools__length_hint__yield from

def no_opt_iter(it):
    yield from it

然后使用ipython %timeit魔法(用不同的常数代替 100):

>>> %%timeit fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))

当输入不够大len(list(it))而导致内存问题时,在运行 Python 3.9 x64 的 Linux 机器上def ilen(it): return len(list(it)),无论输入长度如何,我的解决方案所需的时间都比 长约 50%。

对于最小的输入,加载/调用///的设置成本意味着这种consumeall方式花费的时间比(对于长度为 0 的输入在我的机器上多约 40 ns,比简单方法增加 10%)要长得多,但是通过当您达到长度 2 输入时,成本是相等的,并且在长度 30 左右的某个地方,与实际工作相比,初始开销并不明显;该方法需要大约 50% 的时间。zipcountnextdef ilen(it): sum(1 for _ in it)sumsum

基本上,如果内存使用很重要或输入没有限制大小,并且您更关心速度而不是简洁,请使用此解决方案。如果输入是有界且较小的,len(list(it))可能是最好的,如果它们是无界的,但简单/简洁很重要,你会使用sum(1 for _ in it).

于 2015-12-21T21:22:43.047 回答
9

一个简短的方法是:

def ilen(it):
    return len(list(it))

请注意,如果您要生成大量元素(例如,数万或更多),那么将它们放在列表中可能会成为性能问题。但是,这是对大多数情况下性能无关紧要的想法的简单表达。

于 2011-03-21T22:39:10.910 回答
7

more_itertools是一个实现工具的第三方库ilenpip install more_itertools

import more_itertools as mit


mit.ilen(x for x in range(10))
# 10
于 2017-08-30T21:04:22.120 回答
3
len(list(it))

虽然,如果它是一个无限生成器,它可以挂断。

于 2021-01-08T12:04:00.767 回答
2

我喜欢这个基数包,它非常轻量级,并尝试根据可迭代使用尽可能快的实现。

用法:

>>> 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
于 2016-04-15T10:28:04.150 回答
2

这些将是我的选择之一:

print(len([*gen]))
print(len(list(gen)))
于 2019-06-05T18:28:50.557 回答