2012-11-29 37 views
2

在很多情況下,人們都會說「使用yield懶洋洋地創建元素」。 但我認爲一切都有成本,包括yield及其迭代器。在Python中,何時通過返回列表的收益成本?

在有效的北歐眼中,我認爲這是個好問題。例如,當我得到一個函數。

def list_gen(n): 
    if n > MAGIC_NUM: 
     return xrange(n) 
    else: 
     return range(n) 

MAGIC_NUM的劑量是多少?

UPDATE對這個錯誤抱歉,我的意思是比較迭代器的成本和列表成本。

UPDATE AGAIN請成像一個案例。是否有條件,內存如此限制,無法創建迭代器。哈哈,現在這個問題更加搞笑了。
UPDATE AGAIN爲什麼創建一個迭代器並保存yield字段少於創建列表?或迭代器花費多少錢?(對我的侮辱抱歉)多少字節?

+1

你是說'return xrange(n)'或'return range(n)'對嗎? – Kos

+0

@Kos是的,我正在修復這個錯誤。 – Dreampuf

+3

生成器使用的內存少於列表。因此,如果內存太有限以至於無法創建迭代器,那麼它也無法創建列表。 –

回答

4

你正在混合幾件事。

def list_gen(n): 
    i=0 
    while i<n: 
     yield i 
     i += 1 

這個函數是一個發電機。調用它將返回一個生成器對象,它是一個迭代器

迭代器具有next(),即它可以遍歷一次。每當您執行for i in something時,都會使用iter創建迭代器。

def list_gen(n): 
    return range(n) 

def list_gen(n): 
    return xrange(n) 

這些函數是常規函數。一個返回list,另一個返回一個xrange對象。列表和xrange都是可迭代的,即可以爲它們創建多個獨立的迭代器。


所以回到你的問題:你詢問是否返回listxrange對象。

很明顯,這取決於!這取決於你想要對結果做什麼。

  • 如果你想以某種方式改變它,那麼你需要一個真正的列表。直接使用range

  • 如果只想遍歷它,那麼它不會使語義上的差別:既是xrange對象,並通過range返回將產生一個迭代器,它迭代同一序列的list

    但是,如果您使用xrange,則永遠不會在內存中創建整個列表。爲什麼要在內存中創建一個完整的list對象,如果你只想做一個簡單的迭代?當你想要一個for循環時,你不需要分配一個臨時的大內存緩衝區,對吧?

因此:它是安全的堅持與xrange,因爲主叫方總是可以做出list出來。


讓我們用一個基準來確認。我們想知道迭代xranges的速度是否快於由range(當然包括range調用的成本)構建的列表。

代碼:

import timeit 

ns = [1,2,3, 5, 10, 50, 100] 
print 'n', '\t', 'range', '\t', 'xrange' 
for n in ns: 
    t1 = timeit.timeit("for i in range({}): pass".format(n)) 
    t2 = timeit.timeit("for i in xrange({}): pass".format(n)) 
    print n, '\t', t1, '\t', t2 

結果:

n  range   xrange 
1  0.566222990493 0.418698436395 
2  0.594136874362 0.477882061758 
3  0.630704800817 0.488603362929 
5  0.725149288913 0.540597548519 
10  0.90297752809 0.687031507818 
50  2.44493085566 1.89102105759 
100  4.31189321914 3.33713522433 
+0

很酷。呃,你的結果顯示,至少條件(1元素)列表仍然緩慢迭代器。但是,爲什麼創建一個更快的yield yield上下文然後創建一個列表?什麼是內存成本? – Dreampuf

+2

@Dreampuf:正如glglgl的答案所顯示的,首先製作生成器,然後製作一個列表,比從頭開始列表要慢一些。然而,這是一個很小的差異,關心它是不成熟的優化。過早的優化是萬惡之源。 **總之:如果您不確定,請使用發生器。** –

3

它與您正在生成的迭代器的長度無關,而是與之後需要如何使用它的方式無關。如果你只需要使用它一次,那麼你一定要去產量,如果你多次使用它,你可以跳過產量,只是得到一個常規列表。請記住,使用yield的發電機只能在一次迭代

2

使用yield或發電機主要是風馬牛不相及的列表大小,例如:

  • ,如果你不需要處理整個列表,並可以打破不久,它更有效地使用發電機, 。
  • 模擬無限大小的流,例如素數生成器。

但是,如果您的存儲器有限,例如嵌入式系統,並且無法一次創建整個列表,則必須使用生成器。

至於成本,如果您計算每次調用生成器時對生成器的調用計算成本,則使用生成器會產生額外成本,但使用列表會佔用更多內存,因此您不能一般性地說發電機比列表要好,因爲它涉及內存和性能之間的一些折衷,無論是否使用發電機取決於您的需求和情況。

+0

儘管一切都是正確的,但你根本不解決問題,也不解決提問者,代碼和假設中的錯誤。 –

+0

@LennartRegebro我不同意,顯然OP不知道何時應該使用yield。 – iabdalkader

+0

不,他詢問清單收益的大小變得有用。你不回答。正確答案是「1」。 :-)當你的答案有用時,你的答案也不多,但是當他們有必要時。 –

2

請注意,不能同時使用yieldreturn。函數可以是生成器函數,也可以是普通函數,但不能同時包含兩者。

通常yield避免必須創建一箇中間列表,而是一個接一個地產生元素。例如,當你遞歸地走樹時,這會很有用。看到這個鏈接的例子:http://code.activestate.com/recipes/105873-walk-a-directory-tree-using-a-generator/

發電機的另一種用途是當你想返回許多元素,但你的用戶可能只對前幾個感興趣(例如,G。爲搜索結果)。

避免中間列表將節省內存,但前提是調用者不需要從結果中創建列表。總的來說,它的優點是它可以讓你更加合理地編寫你的生成器函數。

+0

爲什麼downvote? –

+0

這不是我的失望(事實上,我根本看不出倒退),但是現在他的答案已經過時了,因爲他現在從問題中刪除了收益聲明。 –

3

雖然你的問題,它的標題仍然是一種混淆,我會盡量回答它,我理解的方式。

如果你只想迭代的(x)range()的結果,xrange()(特殊對象)比range()(名單)更短以及更長的範圍內更好地:

$ python -m timeit 'a=range(3)' 'for i in a: pass' 
1000000 loops, best of 3: 0.608 usec per loop 
$ python -m timeit 'a=xrange(3)' 'for i in a: pass' 
1000000 loops, best of 3: 0.466 usec per loop 

$ python -m timeit 'a=xrange(30000)' 'for i in a: pass' 
1000 loops, best of 3: 1.01 msec per loop 
$ python -m timeit 'a=range(30000)' 'for i in a: pass' 
1000 loops, best of 3: 1.49 msec per loop 

所以它會更好始終使用xrange()


如果你看一下,一般情況下,它可能會略有不同:你比較「預生產」值/對象,將它們存儲在列表和生產後直接食用這些事後處理它們:

def gen(num): 
    import random 
    i = 0 
    while i < num: 
     value = random.random() 
     yield value 
     i += 1 

def process(value): pass 

def test1(num): 
    data = list(gen(num)) 
    for i in data: process(num) 

def test2(num): 
    for i in gen(num): process(num) 

在這裏它取決於生產和消費如何相互作用,以及開銷有多大。

如果你希望他們獨立行事,你不能同時既「做穿線:

def list_eater(l): 
    while l: 
     yield l.pop(0) 
def test3(num): 
    data = [] 
    def producer(): 
     for i in gen(num): data.append(i) 
    import threading 
    consumerthread = threading.Thread(target=producer) 
    consumerthread.start() 
    while data or consumerthread.isAlive(): 
     for item in list_eater(data): process(item) 
     # Optimizeable. Does idle waiting; a threading.Condition might be quite useful here... 

運行的生產和消耗的所有項目,因爲他們在這裏沒有東北黑鈣土浩長,它需要它們是生產或消費。

+0

我認爲(但我可能錯了)他不僅詢問range(),還討論列表與發電機的一般情況。至少我是這麼理解他的。 –

+0

@LennartRegebro從問題很難說,這是正確的。 – glglgl

+0

對不起,我的表達,@LennartRegebro +1 – Dreampuf