2013-08-28 37 views
4

以下python代碼生成[(0,0),(0,7)...(0,693)]而不是組合所有3和倍數倍數的期望列表7:python生成器的行爲不一致

multiples_of_3 = (i*3 for i in range(100)) 
multiples_of_7 = (i*7 for i in range(100)) 
list((i,j) for i in multiples_of_3 for j in multiples_of_7) 

此代碼解決了這個問題:

list((i,j) for i in (i*3 for i in range(100)) for j in (i*7 for i in range(100))) 

問題:

  1. 發電機對象似乎發揮迭代器的作用,我而不是在每次生成列表都要枚舉時提供迭代器對象。後來的策略似乎被.NET LINQ查詢對象所採用。有沒有一種優雅的方式來解決這個問題?
  2. 第二段代碼如何工作?我應該理解,在循環遍歷所有7的倍數後,生成器的迭代器不會被重置?
  3. 難道你不認爲這種行爲是違反直覺的,如果不矛盾?
+0

閱讀:[Python yield關鍵字解釋](http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained)。 – Bakuriu

回答

2

如你發現,由發電機表達式創建的對象是一個迭代(更準確地說是生成器 - 迭代),設計爲只有一次消耗。如果你需要一個復位發生器,只需創建一個真正的發電機,並在循環使用它:

def multiples_of_3():    # generator 
    for i in range(100): 
     yield i * 3 
def multiples_of_7():    # generator 
    for i in range(100): 
     yield i * 7 
list((i,j) for i in multiples_of_3() for j in multiples_of_7()) 

你的第二個代碼的工作,因爲內環((i*7 ...))的表達名單上外循環的每遍評估。這會導致每次都創建一個新的生成器迭代器,這會給您提供所需的行爲,但代碼的清晰度會降低。

要理解發生了什麼,請記住當for循環迭代它時沒有「重置」迭代器。 (這是一個特徵;這樣的復位將打破遍歷在片大的迭代器,它會爲發電機是不可能的。)例如:

multiples_of_2 = iter(xrange(0, 100, 2)) # iterator 
for i in multiples_of_2: 
    print i 
# prints nothing because the iterator is spent 
for i in multiples_of_2: 
    print i 

...,而不是這樣的:

multiples_of_2 = xrange(0, 100, 2)  # iterable sequence, converted to iterator 
for i in multiples_of_2: 
    print i 
# prints again because a new iterator gets created 
for i in multiples_of_2: 
    print i 

生成器表達式相當於被調用的生成器,因此只能迭代一次。

+0

難道你不認爲如果他們在迭代器而不是迭代器中使用生成器會更有用嗎?事實上,這是最讓我困擾的。我從試驗Haskell和LINQ那裏進行懶惰評估回來,從而在這個過程中節省了內存。 – Tarik

+0

生成器函數*是一般意義上的可迭代的元素 - 通過調用它們將它們轉換爲迭代器。如果通過「生成器」引用*生成器表達式*,它們當然可以實現爲迭代器,其'__iter__'調用不可見的底層生成器函數並生成新的生成器 - 迭代器。但是* *會與普通發電機的工作方式不一致。 (生成器表達式是在常規生成器函數之後引入的 - 包含「yield」的「def」已經是該語言的一部分。) – user4815162342

+0

由於生成器僅僅是語法糖, ,如果使用包含yield的def實現它,會導致它不一致。看看lambda表達式:它們不是作爲幕後實現嗎?再次,只是語法糖。底線是,我仍然不明白我們將生成器實現爲迭代器而不是迭代器會損失什麼。 – Tarik

3

生成器對象的迭代器,因此是一次性的。這不是一個可迭代的它可以產生任意數量的獨立迭代器。這種行爲不是可以在某處切換的東西,所以任何解決方法都等於使用迭代(例如列表)而不是生成器或重複構建生成器。

第二個片段是後者。這是通過定義等同於循環

for i in (i*3 for i in range(100)): 
    for j in (i*7 for i in range(100)): 
     ... 

希望這是不足爲奇的,在這裏,後者發生器表達式重新評價了外部循環的每一次迭代。

+0

「生成器對象是一個迭代器,因此是一次性的,它不是一個可以生成任意數量的獨立迭代器的迭代器。」 +1。我有點理解我的問題。不過,請檢查我剛剛發佈在user4815162342響應下的評論下的新問題。 – Tarik

+0

@Tarik這有點主觀。我可以看到來自LINQ的混淆,但根據我的經驗,很容易避免 - 我知道*它是這樣工作的,但我不記得因爲這個事實而不得不寫代碼。我認爲如果手動*推進迭代器會更容易。例如,考慮'next(g); for x in g:...'跳過第一個項目 - 如果iter(g)不是g',至少需要一個額外的行。請注意,爲了更好的理由,所有其他迭代器的行爲都是相同的,所以更改生成器表達式並不能完全解決這個問題。 – delnan

0

真正的問題,因爲我發現了約單與多遍iterables而事實上,目前還沒有標準的機制來確定是否可迭代的單層或多層通:見Single- vs. Multi-pass iterability

1

如果你想轉換生成器表達式轉換爲多遍迭代器,然後它可以以相當常規的方式完成。例如:

class MultiPass(object): 
    def __init__(self, initfunc): 
     self.initfunc = initfunc 
    def __iter__(self): 
     return self.initfunc() 

multiples_of_3 = MultiPass(lambda: (i*3 for i in range(20))) 
multiples_of_7 = MultiPass(lambda: (i*7 for i in range(20))) 
print list((i,j) for i in multiples_of_3 for j in multiples_of_7) 

從視圖定義的東西的角度這是一個類似工作量的打字:

def multiples_of_3(): 
    return (i*3 for i in range(20)) 

但從用戶的角度來看,他們寫multiples_of_3,而不是multiples_of_3(),這意味着對象multiples_of_3與其他任何可迭代對象是多態的,例如tuplelist

需要鍵入lambda:有點不雅,真實。我不認爲在向語言引入「可迭代理解」時會有任何傷害,在保持向後兼容性的同時給你想要的東西。但是隻有很多標點字符,我懷疑這會被認爲是值得的。

+0

感謝您爲討論添加有趣的觀點。儘管我已經想到了這個解決方案,但我仍然相信迭代器應該始終是多通道的,而迭代器應該是單通道,無論它們是如何創建的(lambda表達式,生成器函數,生成器表達式......) – Tarik

+0

@Tarik:這不是真的可能,因爲你希望能夠提供一個代替iterable的東西,它本質上是single-pass(例如,從stdin或套接字讀取文件對象)。這就是爲什麼迭代器本身是可迭代的(在某些情況下只是返回'self'),以支持該多態性。 –