2014-09-26 21 views
1

中斷髮電機檢測我有這樣的發電機:如何,它已經從外部

def gen(): 
    rounds = 0 
    for x in xrange(10): 
     rounds += 1 
     yield x 
    print 'Generator finished (%d rounds)' % (rounds) 

如果我通常把它叫做:

for x in gen(): 
    pass 

我得到預期:

Generator finished (10 rounds) 

但是如果我中斷髮電機,我什麼也得不到:

for x in gen(): 
    break 

我想獲得:

Generator interrupted (1 rounds) 

是否有可能發現,在發電機本身,它已被從外部中斷?有什麼Exception,我可以趕上發現這個事件?

+0

我懷疑有一個'Exception'。所發生的只是一次調用後該函數停止被調用。因爲這樣的控件永遠不會被返回到執行該'print'的函數。 – rspencer 2014-09-26 10:47:31

+0

不,這裏沒有例外 - 你只是停止循環。你可以通過保留對生成器的引用並確保'next(gen)'提高'StopIteration'來處理它,但是不能從內部看到 - 生成器只是等待,直到它再次被調用或垃圾收集。 – jonrsharpe 2014-09-26 10:49:12

+0

這不是你要求的,但你*可以*包裝在一個上下文管理器中的生成器,並使用'__exit__' - 像這樣:http://ideone.com/X7637V – jedwards 2014-09-26 10:58:40

回答

6

你不能,因爲一個發電機的默認行爲是要一直中斷。一個生成器不斷地暫停,每次它產生一個值。

換句話說,您對發電機工作方式的理解是不正確的。循環發電機完全在發電機的控制之外;所有它的任務是產生下一個值,該值將取消暫停代碼,直到執行下一個yield表達式。因此,當沒有下一個值被請求時,發生器暫停並且不能執行代碼來'檢測'它沒有被要求另一個值。

當你調用一個函數發生器發生是這樣的:

  • 特殊generator iterator object返回。沒有執行函數體中的代碼。
  • 生成器迭代器之外的代碼可能會或可能不會將其用作迭代器。每次需要新值時,調用generator_object.next()
  • 當調用.next()方法時,將運行函數體,直到遇到yield表達式。函數體被暫停,並且yield表達式的結果作爲.next()方法的結果返回。

可以明確發送信息或與generator_object.send()generator_object.throw()方法你發生器功能產生異常,但沒有這樣的消息或異常時發送不遍歷生成器對象。

有一件事你可能尋找的是GeneratorExit當你的generator對象關閉時拋出你的generator函數的異常;看到generator_object.close() method

引發一個GeneratorExit在生成器功能被暫停

當發電機對象垃圾收集的點(例如沒有引用它了)的generator_object.close()方法是自動調用:

def gen(): 
    rounds = 0 
    for x in xrange(10): 
     rounds += 1 
     try: 
      yield x 
     except GeneratorExit: 
      print 'Generator closed early after %d rounds' % rounds 
      raise 
    print 'Generator finished (%d rounds)' % rounds 

演示:

>>> def gen(): 
...  rounds = 0 
...  for x in xrange(10): 
...   rounds += 1 
...   try: 
...    yield x 
...   except GeneratorExit: 
...    print 'Generator closed early after %d rounds' % rounds 
...    raise 
...  print 'Generator finished (%d rounds)' % rounds 
... 
>>> for i in gen(): 
...  break 
... 
Generator closed early after 1 rounds 

這僅適用於返回的生成器對象僅由for循環引用,並且在for循環結束時收穫。

+0

我認爲'GeneratorExit'是足夠的我在尋找。我想如果我有一個關於發生器的引用,它就不會如預期的那樣工作,但是對於只是在原地循環的簡單情況,總是會產生預期的結果。 – dangonfast 2014-09-26 11:59:43

+0

@ jeckyll2hide:考慮到*當生成器關閉時*依賴於Python實現。 CPython使用引用計數並立即收回生成器對象,但在Jython或IronPython中,不再被引用的對象與垃圾收集清除對象之間可能存在顯着的延遲。 – 2014-09-26 12:01:12

0

A contextmanager將允許您提供代碼在發生器出口處執行。

from contextlib import contextmanager 
rounds = 0 

@contextmanager 
def on_generator_exit(): 
    try: 
     yield 
    finally: 
     global rounds 
     print 'Generator finished (%d rounds)' % (rounds) 

def gen(): 
    with on_generator_exit(): 
     global rounds 
     rounds = 0 
     for x in xrange(10): 
      rounds += 1 
      yield x 

for x in gen(): 
    break 

輸出:

Generator finished (1 rounds)