2016-04-20 42 views
1

我正在嘗試編寫一個小的上下文管理器,它會嘗試重複執行一些代碼,直到代碼工作或者直到指定的嘗試次數完成爲止。我試圖寫這個,但遇到上下文管理器在處理問題時遇到困難:Python上下文管理器如何嘗試執行代碼?

Exception RuntimeError: 'generator ignored GeneratorExit' 

我該如何編碼?

import contextlib 
import random 

def main(): 

    with nolube(): 
     print(1/random.randint(0, 1)) 

@contextlib.contextmanager 
def nolube(
    tries = None # None: try indefinitely 
    ): 
    """ 
    Create a context for trying something repeatedly. 
    """ 
    tries_done = 0 
    rekt = True 
    if tries is None: 
     while rekt is True: 
      try: 
       yield 
       rekt = False 
      except: 
       tries_done += 1 
       pass 
    else: 
     while rekt is True and tries_done <= tries: 
      try: 
       yield 
       rekt = False 
      except: 
       tries_done += 1 
       pass 

if __name__ == "__main__": 
    main() 

回答

3

@contextlib.contextmanager有一個非常明確的合同;它只會恢復一次。它不能用於重新運行代碼。

事實上,您不能使用上下文管理器來控制的所有的重複。你需要一個循環,而不是一個上下文管理器。上下文管理器不控制該塊,它只在進入和退出時被通知。

改爲使用tenacity package*;它提供了一個裝飾器。修飾器wraps a function in a while True loop將爲您重新運行該功能。

你會通過移動print()語句轉換成一個功能,再配以@retry,則調用該函數將其應用到你的情況:

import random 
from tenacity import retry 

@retry 
def foo(): 
    print(1/random.randint(0, 1)) 

def main(): 
    foo() 

*這個答案最初建議retrying package但當該項目處於休眠狀態時,這是forked into a new package with updated API

+0

'retrying'包尚未更新一段時間。但是有一個名爲[tenacity]的叉子(https://pypi.python.org/pypi/tenacity)正在維護中。 – TimB

+0

@TimB:謝謝你的提醒,我已經編輯了推薦韌性的答案。 –

1

簡短的回答是:你不能在Python中使用上下文管理器來做到這一點。

我們只能在上下文管理器中使用yield,所以在while循環中使用yield沒有意義。這與Rubyblock s中使用的yield不同。

而且我們也無法訪問代碼主體,例如,我們不會自動獲得可以重複使用的function之類的內容。

所以,不,如果你想要實現一個可重用的邏輯邏輯,請使用函數。

def retry(func, n_times=None): 
    i = 1 
    while True: 
     try: 
      return func() 
     except Exception: 
      i += 1 
      if n_times and i >= n_times: 
       raise 
2

你不能那樣做。 Python中的上下文管理僅僅是其中一個協議:

  1. 呼籲__enter__
  2. 執行一個或多個語句
  3. 呼叫__exit__

第三點是一定會發生的,這使得它的偉大用於處理資源釋放等等。但是重點在於點2:上下文管理器將在上下文中運行代碼,然後處理3.到那時,包裝的代碼已經消失,用於永遠得不到,所以你不能'再次呼叫'。contextlib提供了一個很好的API做它作爲一個功能簡單地定義你的情況管理器:

@contextmanager 
def ctxt(): 
    # 1: __enter__ code 
    yield 
    # 3: __exit__ code 

並明確specifies文檔:

功能正在裝修調用時必須返回一個生成器 - 迭代。此迭代器必須產生一個值,該值將綁定到with語句的as子句中的目標(如果有的話)。

所以前一點仍然存在。

可以做些什麼來repeteadly某事物是把它放在一個功能,和裝飾功能與你的「重複,直到成功」的邏輯:

def dec(f): 
    def decorated(*args, **kwargs): 
     while True: 
      try: 
       return f(*args, **kwargs) 
      except: 
       pass 
    return decorated 

但這是完全無關的背景經理。