2013-12-13 38 views
3

我對Python中的裝飾器很少有經驗,但我想編寫一個運行該函數的函數裝飾器,捕獲特定的異常,並且如果捕獲到異常,然後重試該功能達到一定的次數。也就是說,我想這樣做:陷阱異常,再試一次Python中的裝飾器

@retry_if_exception(BadStatusLine, max_retries=2) 
def thing_that_sometimes_fails(self, foo): 
    foo.do_something_that_sometimes_raises_BadStatusLine() 

我認爲這種事情很容易與裝飾,但我不清楚究竟如何去做。

回答

2
from functools import wraps 
def retry_if_exception(ex, max_retries): 
    def outer(func): 
     @wraps(func) 
     def wrapper(*args, **kwargs): 
      assert max_retries > 0 
      x = max_retries 
      while x: 
       try: 
        return func(*args, **kwargs) 
       except ex: 
        x -= 1 
     return wrapper 
    return outer 

明白爲什麼you better use @wraps

+0

接受換行說明 –

1

我想你基本上想要的東西是這樣的:

def retry_if_exception(exception_type=Exception, max_retries=1): 
    def decorator(fn): 
     def wrapper(*args, **kwargs): 
      for i in range(max_retries+1): 
       print('Try #', i+1) 
       try: 
        return fn(*args, **kwargs) 
       except exception_type as e: 
        print('wrapper exception:', i+1, e) 
     return wrapper 
    return decorator 

@retry_if_exception() 
def foo1(): 
    raise Exception('foo1') 

@retry_if_exception(ArithmeticError) 
def foo2(): 
    x=1/0 

@retry_if_exception(Exception, 2) 
def foo3(): 
    raise Exception('foo3') 
0

爲綱,你會沿着這些路線做一些事情:

import random 

def shaky(): 
    1/random.randint(0,1) 

def retry_if_exception(f): 
    def inner(retries=2): 
     for retry in range(retries): 
      try: 
       return f() 
      except ZeroDivisionError: 
       print 'try {}'.format(retry) 
     raise   

    return inner    

@retry_if_exception 
def thing_that_may_fail(): 
    shaky() 

thing_that_may_fail() 

正如所寫的,這將失敗大約一半的時間。

當它失敗,打印:

try 0 
try 1 
Traceback (most recent call last): 
    File "Untitled 2.py", line 23, in <module> 
    thing_that_may_fail()  
    File "Untitled 2.py", line 10, in inner 
    return f() 
    File "Untitled 2.py", line 21, in thing_that_may_fail 
    shaky() 
    File "Untitled 2.py", line 4, in shaky 
    1/random.randint(0,1) 
ZeroDivisionError: integer division or modulo by zero 

您可以將該結構適應許多不同類型的錯誤。

0

下似乎做你所描述的:

def retry_if_exception(exception, max_retries=2): 
    def _retry_if_exception(method_fn): 
     # method_fn is the function that gives rise 
     # to the method that you've decorated, 
     # with signature (slf, foo) 
     from functools import wraps 
     def method_deco(slf, foo): 
      tries = 0 
      while True: 
       try: 
        return method_fn(slf, foo) 
       except exception: 
        tries += 1 
        if tries > max_retries: 
         raise 
     return wraps(method_fn)(method_deco) 
    return _retry_if_exception 

下面是它在使用中的例子:

d = {} 

class Foo(): 
    def usually_raise_KeyError(self): 
     print("d[17] = %s" % d[17]) 

foo1 = Foo() 

class A(): 
    @retry_if_exception(KeyError, max_retries=2) 
    def something_that_sometimes_fails(self, foo): 
     print("About to call foo.usually_raise_KeyError()") 
     foo.usually_raise_KeyError() 

a = A() 
a.something_that_sometimes_fails(foo1) 

這給:

About to call foo.usually_raise_KeyError() 
About to call foo.usually_raise_KeyError() 
About to call foo.usually_raise_KeyError() 
Traceback (most recent call last): 
    File " ......... TrapRetryDeco.py", line 39, in <module> 
    a.something_that_sometimes_fails(foo1) 
    File " ......... TrapRetryDeco.py", line 15, in method_deco 
    return method_fn(slf, foo) 
    File " ......... TrapRetryDeco.py", line 36, in something_that_sometimes_fails 
    foo.usually_raise_KeyError() 
    File " ......... TrapRetryDeco.py", line 28, in usually_raise_KeyError 
    print("d[17] = %s" % d[17]) 
KeyError: 17 

我認爲通過「2次重試」你的意思是手術將嘗試3次全部告訴。你的例子有一些複雜的可能會掩蓋基本的設置: 看來你想要一個方法裝飾器,因爲你的函數/方法的第一個參數是「self」;然而,該方法立即委託給它的foo參數的一些不好的方法。我保留了這些併發症:)