2015-10-05 54 views
5

我正在試驗2個函數,它們模擬Python 2.x和3.x中內置的zip。第一個返回一個列表(如在Python 2.x的),第二個是發電機功能,它返回一條其結果集在一個時間(如在Python 3.X):使用生成器表達式會導致Python掛起

def myzip_2x(*seqs): 
    its = [iter(seq) for seq in seqs] 
    res = [] 
    while True: 
     try: 
      res.append(tuple([next(it) for it in its])) # Or use generator expression? 
      # res.append(tuple(next(it) for it in its)) 
     except StopIteration: 
      break 
    return res 

def myzip_3x(*seqs): 
    its = [iter(seq) for seq in seqs] 
    while True: 
     try: 
      yield tuple([next(it) for it in its])   # Or use generator expression? 
      # yield tuple(next(it) for it in its) 
     except StopIteration: 
      return 

print(myzip_2x('abc', 'xyz123'))     
print(list(myzip_3x([1, 2, 3, 4, 5], [7, 8, 9]))) 

這運作良好,並給出了預期的輸出zip內置:

[('a', 'x'), ('b', 'y'), ('c', 'z')] 
[(1, 7), (2, 8), (3, 9)] 

然後我想到有關更換tuple()調用其(幾乎)相當於發電機表達式中的列表解析,刪去方括號[](爲什麼當生成器應該適用於可迭代的exp時,使用理解創建臨時列表受tuple()的影響吧?)

但是,這會導致Python掛起。如果執行沒有終止,使用CtrlC(在Windows上的IDLE中),它最終會在幾分鐘後停止,並且(預期的)MemoryError異常。

調試代碼(例如使用PyScripter)顯示當使用生成器表達式時,從不會引發StopIteration異常。第一個示例呼叫上面myzip_2x()不斷增加空元組res,而第二實例包調用myzip_3x()產生元組(1, 7)(2, 8)(3, 9)(4,)(5,)()()()...

我錯過了什麼嗎?

而且最後要注意:如果its成爲每個功能(當列表內涵在tuple()呼叫使用)的第一行(使用its = (iter(seq) for seq in seqs))發電機出現相同的懸掛行爲。

編輯:

謝謝@Blckknght的解釋,你是對的。 This message使用上面的生成器函數的類似示例給出了更多細節。總之,像這樣的生成器表達式只適用於Python 3.5+,它需要文件頂部的from __future__ import generator_stop語句,並在上面更改StopIterationRuntimeError(同樣,使用生成器表達式而不是列表解析時)。

編輯2:

如對上述最後請注意:如果its變成一臺發電機(使用its = (iter(seq) for seq in seqs))將支持只是一個迭代 - 因爲發電機是一次性的迭代器。因此它在第一次運行while循環時耗盡,並且在隨後的循環中僅獲得空元組。

回答

2

你看到的行爲是一個錯誤。它源於這樣一個事實:冒泡發生器的異常與發生器正常退出時無法區分。這意味着你不能在一個生成器上使用tryexcept來包裝一個循環,並且尋找StopIteration將你打破循環,因爲循環邏輯將消耗這個異常。

PEP 479針對這個問題提出了一個解決方案,通過改變語言使發電機內未捕獲的StopIteration變成RuntimeError,然後冒泡。這將允許你的代碼工作(通過對你捕獲的異常類型的小調整)。

PEP已經在Python 3.5中實現,但爲了保持向後兼容性,更改的行爲僅在您通過將from __future__ import generator_stop置於文件頂部的要求時纔可用。新的行爲將被默認在Python 3.7中啓用(Python的3.6將默認爲舊的行爲,但如果這種情況出現,可能發出警告)。

0

當你這樣做:

tuple([next(it) for it in its]) 

你首先創建一個列表,然後將它傳遞給tuple()。如果由於引發StopIteration而無法創建列表,則不會創建列表並傳播異常。

但是,當你這樣做:

tuple(next(it) for it in its) 

你正在構建一臺發電機,並直接將其傳遞給tuple()。元組構造函數將使用生成器作爲迭代器:即,將查看項直到StopIteration被引發。

也就是說,StopIterationtuple()抓住,不傳播。

立即引發StopIteration的發電機轉換爲空元組。

0

我真的不知道這件事,但它看起來像你嵌套了發電機和外一個捕捉StopIteration通過內部凸起。

考慮這個例子:

def gen(its): 
    for it in its: 
     yield next(it) # raises StopIteration 

tuple(gen(its)) # doesn't raises StopIteration 

它做的東西等於你的版本做什麼。

2

下面是基於這些代碼,而不是Python語言引用或參考實現運行時的行爲猜測。

表達式tuple(next(it) for it in its)相當於tuple(generator)其中generator = (next(it) for it in its)。該tuple構造是概念等同於以下代碼:

def __init__(self, generator): 
    for element in generator: 
     self.__internal_array.append(element) 

因爲for聲明捕捉任何StopIteration爲疲憊的跡象,當發電機引發StopIteration因爲next(it)提高它的for聲明只會抓住它並認爲發電機已耗盡。這就是爲什麼循環永遠不會結束,並且追加空元組:異常不會冒起tuple構造函數。

列表內涵,[next(it) for it in its],在另一方面,是概念相當於

result = [] 
for it in its: 
    result.append(next(it)) 

所以StopIteration不被for語句捕捉到。

該實施例顯示字面理解和與發電機表達構造函數調用之間的有趣的非平凡的差。如果使用list(next(it) for it in its vs [next(it) for it in its],則會發生同樣的情況。

+0

我可以用任何環路或功能之外運行的代碼確認你的猜測。 –

+0

謝謝你提供這樣一個好的概念性解釋。 – John