2016-02-18 74 views
5

我有一段代碼嘗試訪問資源,但有時不可用,並導致異常。我嘗試使用上下文管理器實現重試引擎,但我無法處理__enter__上下文形式上下文管理器中由調用者引發的異常。處理上下文管理器中的異常

class retry(object): 
    def __init__(self, retries=0): 
     self.retries = retries 
     self.attempts = 0 
    def __enter__(self): 
     for _ in range(self.retries): 
      try: 
       self.attempts += 1 
       return self 
      except Exception as e: 
       err = e 
    def __exit__(self, exc_type, exc_val, traceback): 
     print 'Attempts', self.attempts 

這是一些例子,只是拋出一個異常(我希望處理哪一個)

>>> with retry(retries=3): 
...  print ok 
... 
Attempts 1 
Traceback (most recent call last): 
    File "<stdin>", line 2, in <module> 
NameError: name 'ok' is not defined 
>>> 
>>> with retry(retries=3): 
...  open('/file') 
... 
Attempts 1 
Traceback (most recent call last): 
    File "<stdin>", line 2, in <module> 
IOError: [Errno 2] No such file or directory: '/file' 

有沒有辦法攔截這個異常(S)和處理這裏面的情況管理器?

回答

8

引用__exit__

如果有異常供給,方法希望抑制異常(即防止它被傳播),它應該返回一個真實值。否則,在退出此方法後,異常將被正常處理。

默認情況下,如果您沒有顯式地從函數返回值,Python將返回None,這是一個虛假值。在你的情況下,__exit__返回None,這就是爲什麼允許流言流過__exit__

因此,返回truthy值,這樣

class retry(object): 

    def __init__(self, retries=0): 
     ... 


    def __enter__(self): 
     ... 

    def __exit__(self, exc_type, exc_val, traceback): 
     print 'Attempts', self.attempts 
     print exc_type, exc_val 
     return True         # or any truthy value 

with retry(retries=3): 
    print ok 

輸出將是

Attempts 1 
<type 'exceptions.NameError'> name 'ok' is not defined 

如果你想有重試功能,可以實現與發電機,像這樣

def retry(retries=3): 
    left = {'retries': retries} 

    def decorator(f): 
     def inner(*args, **kwargs): 
      while left['retries']: 
       try: 
        return f(*args, **kwargs) 
       except NameError as e: 
        print e 
        left['retries'] -= 1 
        print "Retries Left", left['retries'] 
      raise Exception("Retried {} times".format(retries)) 
     return inner 
    return decorator 


@retry(retries=3) 
def func(): 
    print ok 

func() 
+0

但我希望它返回 「嘗試3」,而不是1個 –

+1

@MauroBaraldi那是不可能的上下文管理。你可能想使用一個裝飾器。 – thefourtheye

+1

@MauroBaraldi我收錄了一個退休的樣本程序。 PTAL。 – thefourtheye

1

要在__enter__方法處理異常時,最簡單的(和那麼令人驚訝。)做的事,將包裹with語句本身在try-except子句,並簡單地拋出異常 -

但是,with塊definetelly沒有設計這樣的工作 - 是,自己「retriable」 - 並有一些誤解的位置:

def __enter__(self): 
    for _ in range(self.retries): 
     try: 
      self.attempts += 1 
      return self 
     except Exception as e: 
      err = e 

一旦你回到self那裏,上下文是__enter__運行不再存在 - 如果在with塊內發生錯誤,它將自然流向__exit__方法。並且,不管怎樣,__exit__方法不能使執行流程回到with塊的開頭。

你可能會想要更多的東西是這樣的:

class Retrier(object): 
    max_retries = 3 
    def __init__(self, ...): 
     self.retries = 0 
     self.acomplished = False 
    def __enter__(self): 
     return self 
    def __exit__(self, exc, value, traceback): 
     if not exc: 
      self.acomplshed = True 
      return True 
     self.retries += 1 
     if self.retries >= self.max_retries: 
      return False 
     return True 
.... 
x = Retrier() 
while not x.acomplished: 
    with x: 
     ... 
0

我認爲這很容易,其他人似乎正在推翻它。只需將資源獲取代碼放入__enter__,並嘗試返回,而不是self,但獲取資源。在代碼:

def __init__(self, retries): 
    ... 
    # for demo, let's add a list to store the exceptions caught as well 
    self.errors = [] 

def __enter__(self): 
    for _ in range(self.retries): 
     try: 
      return resource # replace this with real code 
     except Exception as e: 
      self.attempts += 1 
      self.errors.append(e) 

# this needs to return True to suppress propagation, as others have said 
def __exit__(self, exc_type, exc_val, traceback): 
    print 'Attempts', self.attempts 
    for e in self.errors: 
     print e # as demo, print them out for good measure! 
    return True 

立即嘗試:

>>> with retry(retries=3) as resource: 
...  # if resource is successfully fetched, you can access it as `resource`; 
...  # if fetching failed, `resource` will be None 
...  print 'I get', resource 
I get None 
Attempts 3 
name 'resource' is not defined 
name 'resource' is not defined 
name 'resource' is not defined