方法,该方法在iterable可能很长时比sum(1 for i in it)快(在iterable很短时不比len(list(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
def ilen(it):
# Make a stateful counting iterator
cnt = count()
# zip it with the input iterator, then drain until input exhausted at C level
deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far
# Since count 0 based, the next value is the count
return next(cnt)
对于最小的输入,调用deque/zip/count/next的设置成本意味着这样做比def ilen(it): sum(1 for x in it)花费的时间要长得多(在我的机器上,长度为0的输入要多200纳秒,比简单的sum方法多33%),但对于更长的输入,每个额外元素的运行时间大约是前者的一半;对于长度为5的输入,成本是相等的,并且在长度为50-100的范围内,与实际工作相比,初始开销是不明显的;sum方法大约需要两倍的时间。
基本上,如果内存使用问题或输入没有限制大小,并且您关心的是速度而不是简洁性,那么使用这个解决方案。如果输入是有界的并且很小,len(list(it))可能是最好的,如果它们是无界的,但是简单性/简洁性很重要,那么可以使用sum(1 for x in it)。
一条捷径是:
请注意,如果您正在生成元素的lot(例如,数万个或更多),那么将它们放入列表可能会成为性能问题。然而,这只是一个简单的想法表达,在大多数情况下,性能并不重要。
通常的方法是
方法,该方法在iterable可能很长时比
sum(1 for i in it)
快(在iterable很短时不比len(list(it))
慢),同时保持固定的内存开销行为(与len(list(it))
不同),以避免为较大的输入交换抖动和重新分配开销:与
len(list(it))
一样,它在CPython上执行C代码循环(deque
、count
和zip
都在C中实现);避免每个循环执行字节代码通常是CPython性能的关键。很难找到公平的测试用例来比较性能(
list
欺骗使用的__length_hint__
不太可能适用于任意输入的iterable,itertools
不提供__length_hint__
的函数通常有特殊的操作模式,当每个循环返回的值在释放之前释放时工作得更快下一个值被请求,使用maxlen=0
的deque
将执行此操作)。我使用的测试用例是创建一个生成器函数,该函数接受输入并返回一个缺少特殊的itertools
返回容器优化或__length_hint__
的C级生成器,使用Python 3.3的yield from
:然后使用
ipython
%timeit
魔术(用不同的常数替换100):当输入不够大以至于
len(list(it))
会导致内存问题时,在运行Python 3.5x64的Linux机器上,无论输入长度如何,我的解决方案比def ilen(it): return len(list(it))
长大约50%。对于最小的输入,调用
deque
/zip
/count
/next
的设置成本意味着这样做比def ilen(it): sum(1 for x in it)
花费的时间要长得多(在我的机器上,长度为0的输入要多200纳秒,比简单的sum
方法多33%),但对于更长的输入,每个额外元素的运行时间大约是前者的一半;对于长度为5的输入,成本是相等的,并且在长度为50-100的范围内,与实际工作相比,初始开销是不明显的;sum
方法大约需要两倍的时间。基本上,如果内存使用问题或输入没有限制大小,并且您关心的是速度而不是简洁性,那么使用这个解决方案。如果输入是有界的并且很小,
len(list(it))
可能是最好的,如果它们是无界的,但是简单性/简洁性很重要,那么可以使用sum(1 for x in it)
。相关问题 更多 >
编程相关推荐