方法這是有意義的速度比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
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)
喜歡len(list(it))
它執行上CPython的C代碼環路(deque
,count
和zip
在C中的所有實現的);避免每個循環的字節碼執行通常是CPython性能的關鍵。
這是令人驚訝的困難拿出公平的測試用例性能對比(list
騙子使用__length_hint__
這是不太可能可用於任意輸入iterables,itertools
功能,不提供__length_hint__
經常有工作的特殊操作模式當每個循環上返回的值在請求下一個值之前釋放釋放時更快,其中deque
與maxlen=0
將執行)。我使用的測試案例是創造一個發電機的功能,將採取的輸入,並返回一個C水平發生器,缺乏特殊itertools
返回容器優化或__length_hint__
,使用Python 3.3的yield from
:
def no_opt_iter(it):
yield from it
然後使用ipython
%timeit
魔術(100替換不同的常量):
>>> %%timeit -r5 fakeinput = (0,) * 100
... ilen(no_opt_iter(fakeinput))
當輸入不夠大,len(list(it))
會導致內存問題,上運行的Python 3.5 x64的Linux中,我的解決方案需要大約長50%THA無論輸入的長度如何,都可以使用def ilen(it): return len(list(it))
。
對於最小的輸入,設置成本調用deque
/zip
/count
/next
意味着需要無限長這種方式比def ilen(it): sum(1 for x in it)
(約200納秒更我的機器上用於長度爲0的輸入,這是一個33%的在簡單的sum
方法中增加),但對於較長的輸入,其運行時間約爲每增加一個元素的一半;對於長度爲5的輸入,成本是等同的,並且在長度爲50-100的範圍內,與實際工作相比,初始開銷是不明顯的; sum
方法需要大約兩倍的時間。
基本上,如果內存使用問題或輸入不具有有限大小,並且您關心速度而不是簡潔性,請使用此解決方案。如果投入是有限的,並且很小,len(list(it))
可能是最好的,如果它們是無限的,但簡單/簡潔計數,則可以使用sum(1 for x in it)
。
請不要使用'_'作爲變量名,因爲:(1)它往往會迷惑人們,使他們認爲這是某種特殊的語法,(2)與'_'碰撞交互式解釋器和(3)與常見的gettext別名衝突。 – 2011-03-21 22:39:32
@Sven:我一直使用'_'作爲未使用的變量(來自Prolog和Haskell編程的習慣)。 (1)是首先提出這個問題的原因。我沒有考慮(2)和(3),謝謝你指出它們! – 2011-03-21 22:47:04
重複:http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-the-length-of-an-iterable-in-python – tokland 2011-03-21 22:47:16