Python: 重试三次函数直到全部失败

3 投票
4 回答
6142 浏览
提问于 2025-04-18 15:50

我正在使用 Python 2.7,遇到了以下情况。我想尝试调用一个函数三次。如果这三次都出错了,我会抛出最后一次的错误。如果其中有一次成功了,我就会停止尝试,立刻继续后面的操作。

这是我现在的代码:

output = None
error = None
for _e in range(3):
    error = None
    try:
        print 'trial %d!' % (_e + 1)
        output = trial_function()
    except Exception as e:
        error = e
    if error is None:
        break
if error is not None:
    raise error

有没有更好的代码片段可以实现同样的功能呢?

4 个回答

1

我发现了一种很简单的重试方法。这里有一个叫做retry的模块。

  • 首先,你需要安装这个模块,可以用下面的命令:

    pip install retry
    
  • 然后在你的代码里导入这个模块。

    from retry import retry
    
  • 在方法上方使用@retry这个装饰器。我们可以给这个装饰器传一些参数,比如tries(重试次数)、delay(每次重试的间隔时间)和Exception(指定要重试的错误类型)。

示例

    from retry import retry

    @retry(AssertionError, tries=3, delay=2)
    def retryfunc():
        try:
            ret = False
            assert ret, "Failed"
        except Exception as ex:
            print(ex)
            raise ex
  • 上面的代码每次都会失败,但使用了重试装饰器后,它会在失败时重试3次,每次重试之间间隔2秒。而且这个重试只针对断言失败的情况,因为我们指定了错误类型为AssertionError,如果是其他类型的错误,函数就不会重试。
3

忽略调试输出和古老的Python语法,这段代码看起来不错。我唯一想改的就是把它放进一个函数里,这样你就可以直接返回trial_function()的结果了。另外,error = None就变得不必要了,相关的检查也可以省略。如果循环结束了,error一定是被设置过的,所以你可以直接抛出它。如果你不想用函数,可以考虑在for循环中使用else,并在得到第一个结果后直接跳出循环。

for i in range(3):
    try:
        result = foo()
        break
    except Exception as error:
        pass
else:
    raise error
use_somehow(result)

当然,建议在函数中使用装饰器的想法依然适用。你也可以在本地应用这个,毕竟装饰器的语法只是语法糖而已:

# retry from powerfj's answer below
rfoo = retry(3)(foo)
result = rfoo()
6

这里有一种可能的解决方法:

def attempt(func, times=3):
    for _ in range(times):
        try:
            return func()
        except Exception as err:
            pass
    raise err

下面是一个包含print语句的示例:

>>> attempt(lambda: 1/0)
Attempt 1
Attempt 2
Attempt 3

Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    attempt(lambda: 1/0)
  File "<pyshell#17>", line 8, in attempt
    raise err
ZeroDivisionError: integer division or modulo by zero

如果你在使用Python 3.x时遇到UnboundLocalError错误,可以这样调整:

def attempt(func, times=3):
    to_raise = None
    for _ in range(times):
        try:
            return func()
        except Exception as err:
            to_raise = err
    raise to_raise

这是因为try语句结束时,err会被清空;根据文档的说明:

当异常通过as target被赋值后,它会在except语句块结束时被清除。

7

使用装饰器

from functools import wraps

def retry(times):

    def wrapper_fn(f):

        @wraps(f)
        def new_wrapper(*args,**kwargs):
            for i in range(times):
                try:
                    print 'try %s' % (i + 1)
                    return f(*args,**kwargs)
                except Exception as e:
                    error = e
            raise error

        return new_wrapper

    return wrapper_fn

@retry(3)
def foo():
    return 1/0;

print foo()

撰写回答