2010-04-23 103 views
11

我正在尋找迭代第一個n迭代項的pythonic方式(upd:不是在常見情況下的列表,因爲列表中的東西是微不足道的),而且儘快做到這一點非常重要。這就是我現在的做法:快速迭代python中的迭代(不是列表)的前n項目

count = 0 
for item in iterable: 
do_something(item) 
count += 1 
if count >= n: break 

對我來說看起來並不整齊。這樣做的另一種方法是:

for item in itertools.islice(iterable, n): 
    do_something(item) 

這看起來不錯,問題是它足夠快以至於與某些發生器一起使用?例如:

pair_generator = lambda iterable: itertools.izip(*[iter(iterable)]*2) 
for item in itertools.islice(pair_generator(iterable), n): 
so_something(item) 

與第一種方法相比,它運行速度是否足夠快? 有沒有更簡單的方法來做到這一點?

+3

只有這樣,才能回答「不夠快」是標杆它自己。 – 2010-04-23 21:51:15

+0

另請參閱:http://stackoverflow.com/questions/2688079/how-to-iterate-over-the-first-n-elements-of-a-list – outis 2010-04-23 21:57:03

+0

爲什麼「相當重要的做到這一點儘快可能」?你可以用pstats結果來證明這是一個真實的用例嗎?我懷疑你用'islice'的解決方案實際上會證明在性能方面是最合理的解決方案,但是我們當然不知道沒有時間。 – 2010-04-23 22:02:46

回答

14

for item in itertools.islice(iterable, n):是最明顯的,簡單的方法去做吧。它適用於任意迭代,並且是O(n),就像任何理智的解決方案一樣。

可以想象,另一種解決方案可能會有更好的性能;我們不知道沒有時間。除非你的profile你的代碼,並找到這個電話是一個熱點,我不會建議打擾時間。除非它被埋藏在內部循環之中,否則它將是非常值得懷疑的。不成熟的優化是萬惡之源。


如果我要去尋找替代解決方案,我會看的像for count, item in enumerate(iterable): if count > n: break ...for i in xrange(n): item = next(iterator) ...。我不會猜測這些會有所幫助,但如果我們真的想比較一下,他們似乎值得嘗試。如果我被困在的情況下,我發現並且發現這是內部循環中的一個熱點(這真的是你的情況嗎?),我也會嘗試從獲得全局iteroolsislice屬性來減輕名稱查找已經將該函數綁定到本地名稱。

這些東西你只有在你證明他們會提供幫助後才能做到。人們嘗試了很多次。它並沒有幫助他們的節目明顯加快。它只會讓他們的計劃變得更糟。

+1

非常感謝您的回答,它幫助了我很多! – martinthenext 2010-04-23 22:05:12

+0

恩,枚舉對我來說看起來相當不錯!至於分析和查找熱點,這實際上並不是我的情況,我只是希望我的代碼中有一些循環有大量的迭代計數,這就是爲什麼我提出了一個問題。現在我明白了 - 在這個階段嘗試優化是一個錯誤,我必須完成代碼並對其進行測試,並且只有在需要時才進行優化。再次感謝你的幫助。 – martinthenext 2010-04-23 22:27:31

1

的列表?嘗試

for k in mylist[0:n]: 
    # do stuff with k 

你也可以使用一個理解,如果你需要

my_new_list = [blah(k) for k in mylist[0:n]] 
+0

是的,我明白了。錯誤的標題,對不起,我的壞。 – martinthenext 2010-04-23 21:52:55

2

如果它是一個列表,那麼你可以用切片:

list[:n] 
6

itertools當直接適用時,往往是最快的解決方案。

顯然,要檢查的唯一方法就是標杆 - 例如,保存aaa.py

import itertools 

def doit1(iterable, n, do_something=lambda x: None): 
    count = 0 
    for item in iterable: 
    do_something(item) 
    count += 1 
    if count >= n: break 

def doit2(iterable, n, do_something=lambda x: None): 
    for item in itertools.islice(iterable, n): 
     do_something(item) 

pair_generator = lambda iterable: itertools.izip(*[iter(iterable)]*2) 

def dd1(itrbl=range(44)): doit1(itrbl, 23) 
def dd2(itrbl=range(44)): doit2(itrbl, 23) 

看看...:

$ python -mtimeit -s'import aaa' 'aaa.dd1()' 
100000 loops, best of 3: 8.82 usec per loop 
$ python -mtimeit -s'import aaa' 'aaa.dd2()' 
100000 loops, best of 3: 6.33 usec per loop 

很清楚,itertools在這裏速度更快 - 用您自己的數據進行基準驗證。

順便說一句,我覺得timeit更多的命令行可用,所以這就是我總是使用它 - 它然後運行正確的「數量級」的循環,你專門試圖測量的速度類型,那些是10,100,1000等等 - 在這裏,爲了區分差異的一個微秒和一半,十萬個循環是正確的。

+1

奇怪,這與我看來,簡單的解決方案運行速度比純粹的解決方案要慢。蟒蛇確實是最酷的語言。這是麥克格雷厄姆建議不要做過早優化的一個很好的補充。我想一般的規則是寫出簡潔的內容,而不是考慮跑步時間。 – martinthenext 2010-04-23 22:15:20

+1

@馬丁,個人而言,我認爲關於運行時間的一個_lot_(主要是針對可擴展性的big-O) - 但是,一般來說,大多數Pythonic成語通常會是最優化的成語,因爲它們通常是Python提交者最關心的人(Hettinger,itertools和其他Python的快速部分的作者,近年來在這一領域一直很活躍,就像Peters早年的那樣,但它是一個相當普遍的Python提交者社區中的現象)。 – 2010-04-24 02:26:08

2

您可以使用enumerate基本上寫你有相同的循環,但在一個更簡單,Python的方式:

 
for idx, val in enumerate(iterableobj): 
    if idx > n: 
     break 
    do_something(val) 
+0

這個選項已經在上面討論過了,看起來是個不錯的選擇,但我認爲'islice'更好,因爲它不需要循環體中的任何額外的變量,使它看起來更清晰 – martinthenext 2010-04-23 22:37:45