yield
与正则表达式匹配的行。那么,在不关心元素本身的情况下,我该如何计算可迭代对象中的项数?
在Python 2中调用itertools.imap()
或在Python 3中调用map()
的函数可以被等价的生成器表达式所替代:
sum(1 for dummy in it)
这也使用了惰性生成器,因此它避免在内存中材料化所有迭代器元素的完整列表。
len(list(it))
——如果元素是唯一的,那么使用len(set(it))
可以节省一个字符。 - F1Rumorslen(list(it))
是可以的。但是当你有一个产生大量元素的惰性迭代器时,你不想在同一时间将它们全部存储在内存中进行计数,这种情况下使用此答案中的代码可以避免这个问题。 - Sven Marnach在可迭代对象很长的情况下比 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 代码中执行循环(deque
、count
和 zip
都是用 C 实现的)。避免每次循环的字节码执行通常是 CPython 性能的关键。
想要比较性能,找到公平的测试用例非常困难(list
使用 __length_hint__
欺骗,而这在任意输入可迭代对象中都不太可能存在;不提供 __length_hint__
的 itertools
函数经常具有特殊的操作模式,当每个循环返回的值在请求下一个值之前得到释放/释放时会更快,可以使用带有 maxlen=0
的 deque
实现)。我使用的测试用例是创建一个生成器函数,该函数将采用输入并返回缺少特殊 itertools
返回容器优化或 __length_hint__
的 C 级别生成器,使用 Python 3.3+ 的 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
/zip
/count
/next
的设置成本意味着这种方式所需的时间比def ilen(it): sum(1 for _ in it)
略微长一些(在我的机器上为长度为0的输入增加了约40 ns,相当于简单的sum
方法的10%),但是当你将输入长度增加到2时,成本就变得相当,而在长度为30左右时,初始开销与真正的工作相比微不足道;使用sum
方法需要的时间大约多出50%。len(list(it))
可能最佳,如果它们是无界的,则使用sum(1 for _ in it)
可以使代码更简洁。more_itertools.ilen
中的实现。 - rsalmeiconsume
食谱不是它的复制部分(实际上,我稍微调整了我实现的consume
子集的方式,但它几乎没有什么原创性)。被复制的是使用zip
和itertools.count
以一种避免存储zip
结果的方式(从而启用重用tupel
而不是分配新tupel
的zip
优化)的consume
。more-itertools
#230中提出的实现几乎是从此代码在其开放时的近似复制。 - ShadowRangerdef ilen(it):
return len(list(it))
sum(1 for i in it)
更快。 - Sven Marnachlen(it)
不起作用。 sum(it)
、max(it)
、min(it)
等都按预期工作,只有 len(it)
不行。 - Kai Petzkeit
是一个迭代器时,没有保证它知道自己的长度而不运行它。最明显的例子是文件对象;它们的长度基于文件中的行数,但是行的长度是可变的,知道有多少行的唯一方法就是读取整个文件并计算换行符的数量。len()
旨在成为一种廉价的O(1)
操作; 当你要求它们的长度时,你想让它静默地读取多GB的文件吗?sum
、max
和min
是必须读取它们的数据的聚合函数,而len
则不是。 - ShadowRangercount(it)
。 - Kai Petzkemore_itertools
是一个第三方库,它实现了一个名为ilen
的工具。使用pip install more_itertools
进行安装。
import more_itertools as mit
mit.ilen(x for x in range(10))
# 10
len(list(it))
不过,如果它是一个无限生成器,它会挂起。
>>> 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
print(len([*gen]))
print(len(list(gen)))
list
之前增加扩展整个生成器的开销。也就是说,除非您能解释第一种选择有什么优点,否则此答案不会增加任何价值。 - jpmc26len([*gen])
非常简短。例如,在“代码高尔夫”比赛中,这将非常有价值。但是,我同意您的看法,在大多数用例中,这种解决方案不是最优的。 - ruancomellilen([*gen])
对我来说感觉不太符合 Python 的风格。 - ruancomellilist
函数来创建一个列表,与使用[*gen]
的技巧是相同的。例如,在我的机器上,python -m timeit "len([*(_ for _ in range(100))])"
显示出比python -m timeit "len(list(_ for _ in range(100)))"
稍微更好的性能(可能是因为不需要在全局命名空间中查找list
这个名称)。 - Karl Knechtel如果您想在其他地方使用可迭代对象并知道消耗了多少元素,可以创建一个简单的包装类:
from collections.abc import Iterable, Iterator
from typing import Generic, TypeVar
_T = TypeVar("_T")
class IterCounter(Generic[_T]):
"""Iterator that keeps count of the consumed elements"""
def __init__(self, iterable: Iterable[_T]) -> None:
self._iterator = iter(iterable)
self.count = 0
def __iter__(self) -> Iterator[_T]:
return self
def __next__(self) -> _T:
element = next(self._iterator)
self.count += 1
return element
counter = IterCounter(range(5))
print(counter.count) # 0
print(list(counter)) # [0, 1, 2, 3, 4]
print(counter.count) # 5
_
作为变量名,因为 (1) 它往往会使人们产生混淆,误以为这是一种特殊的语法;(2) 与交互式解释器中的_
重名;(3) 与常见的 gettext 别名冲突。请使用其他合适的变量名代替。 - Sven Marnachpython 3.x
, if there exits repeated items and you also want to check the count for each item, useCounter(generator/iterator)
, eg.,c = Counter(iter('goodbadugly'))
, then count the total:sum(c.values())
- Kuo