使用Python装饰器来重试代码块

我们可以用装饰器来修改一个函数或类,以扩展该函数的行为,而不永久改变它。本文讨论了如何使用retry 装饰器来修改一个现有的函数,而不对上述函数进行修改。

在这种情况下,修改后的函数在给定的情况下重试几次,其返回值可能与我们想要的不同。

retry 装饰器的重要性

我们可以使用装饰器来扩展特定函数的行为,我们可以很容易地创建装饰器来修改该函数,即使我们不能访问它或不想改变它。

我们可能经常需要那个函数相当具体的方式,这就是Python装饰器的用处。所以让我们创建一个简单的函数来展示装饰器的工作原理。

这个简单的函数,quotient() ,接受两个参数并将第一个参数除以第二个参数。

def quotient(a, b):
    return a / b
print(quotient(3, 7))

输出:

0.42857142857142855

然而,如果我们希望除法的结果总是被除数较大(所以结果将是2.3333333333333335 ),我们可以改变代码或利用decorators

通过装饰器,我们可以扩展函数的行为而不改变其代码块。

def improv(func):
    def inner(a, b):
        if a < b:
            a, b = b, a
        return func(a, b)
    return inner
@improv
def quotient(a, b):
    return a / b
print(quotient(3, 7))

输出:

2.3333333333333335

improv() 函数是装饰器函数,它以quotient() 函数为参数,并持有一个内部函数,该函数接受quotient() 函数的参数,并带来你需要添加的额外功能。

现在,通过装饰器,我们可以在某个特定的函数上添加retry 功能,特别是对于我们无法访问的函数。

retry decorators在可能存在不可预测的行为或错误的情况下很有帮助,当它们发生时,你想再次重试那个相同的操作。

一个典型的例子是在一个for 循环中处理一个失败的请求。在这种情况下,我们可以使用retry 装饰器来管理重试该特定请求的指定次数。

在 Python 中使用@retry 来重试代码块

对于retry 装饰器,有不同的库提供这种功能,其中一个库是retrying 库。

有了它,你可以从Exceptions ,指定waitstop 的条件,以预期返回结果。要安装retrying 库,我们可以使用pip 命令,如下所示:

pip install retrying

现在,让我们创建一个函数,随机创建010 之间的数字,但当我们遇到数字大于1 的情况时,会引发一个ValueError

import random
def generateRandomly():
    if random.randint(0, 10) > 1:
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."
print(generateRandomly())

如果当时生成的数字大于1,那么代码输出将如下所示。

Traceback (most recent call last):
  File "c:UsersakinlDocumentsPythonSFTPtest.py", line 11, in <module>
    print(generateRandomly())
  File "c:UsersakinlDocumentsPythonSFTPtest.py", line 6, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one

我们的代码中不能出现抛出ValueError 的情况,所以我们可以引入一个retry 装饰器来重试generateRandomly() 函数,直到它没有引发一个ValueError

import random
from retrying import retry
@retry
def generateRandomly():
    if random.randint(0, 10) > 1:
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."
print(generateRandomly())

输出:

Finally Generated.

现在,retry 装饰器重试random 操作,直到它没有出现ValueError ,而我们只有字符串Finally Generated.

我们可以通过在if 块中引入一个print() 语句来查看代码重试generateRandomly() 的次数。

import random
from retrying import retry
@retry
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."
print(generateRandomly())

输出:

1
1
1
1
1
1
1
Finally Generated.

在这里,8 ,但当你运行代码时,它可能是不同的。然而,我们不能出现代码长时间不断重试的情况。所以,我们有stop_max_attempt_numberstop_max_delay 等参数。

import random
from retrying import retry
@retry(stop_max_attempt_number=5)
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."
print(generateRandomly())

输出:

1
1
1
Finally Generated.

该函数只重试了5 次,但要么在第五次之前获得成功,返回值Finally Generated. ,要么不成功,抛出ValueError

1
1
1
1
1
File "C:Python310libsite-packagesretrying.py", line 247, in get
    six.reraise(self.value[0], self.value[1], self.value[2])
  File "C:Python310libsite-packagessix.py", line 719, in reraise
    raise value
  File "C:Python310libsite-packagesretrying.py", line 200, in call
    attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
  File "c:UsersakinlDocumentsPythonSFTPtest.py", line 9, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one

在Python中使用tenacity 来重试代码块

retrying 库可能有点古怪,而且不再维护,但tenacity 库提供了它的所有功能,并有更多的工具可供使用。

要安装tenacity ,使用下面的pip 命令:

pip install tenacity

我们可以用stop 尝试同样的代码,3

import random
from tenacity import retry, stop_after_attempt
@retry(stop=stop_after_attempt(3))
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."
print(generateRandomly())

如果在三次尝试的时间范围内,生成的随机数小于1,则代码的输出。

1
Finally Generated.

然而,如果不是,就会抛出下面的输出。

1
1
1
Traceback (most recent call last):
  File "C:Python310libsite-packagestenacity__init__.py", line 407, in __call__
    result = fn(*args, **kwargs)
  File "c:UsersakinlDocumentsPythonSFTPtest.py", line 9, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one
The above exception was a direct cause of the following exception:
Traceback (most recent call last):
  File "c:UsersakinlDocumentsPythonSFTPtest.py", line 14, in <module>
    print(generateRandomly())
  File "C:Python310libsite-packagestenacity__init__.py", line 324, in wrapped_f
    return self(f, *args, **kw)
  File "C:Python310libsite-packagestenacity__init__.py", line 404, in __call__
    do = self.iter(retry_state=retry_state)
  File "C:Python310libsite-packagestenacity__init__.py", line 361, in iter
    raise retry_exc from fut.exception()
tenacity.RetryError: RetryError[<Future at 0x29a75442c20 state=finished raised ValueError>]