tenacity 报错_Python tenacity源码分析(一个专门用来做重试的库)v1.0

本文介绍了Python的tenacity库,一个用于错误重试的工具。通过装饰器@retry,tenacity可以自动重试函数,支持设定重试次数、间隔等。文章分析了tenacity v1.0.0的源码,包括retry装饰器的工作原理和Retrying类的核心方法,如stop、wait策略以及should_reject的判断逻辑。
摘要由CSDN通过智能技术生成

tenacity是干什么用的

tenacity是Python中一个专门用来进行错误重试的库。我们在执行一些不稳定操作的时候如果抛异常,一般会选择重试几次,比如爬虫使用代理ip请求目标页面时就有可能因为代理ip失效造成响应异常 ,这时tenacity这个库就派上用上了。

tenacity的使用方法

tenacity使用起来非常简单,如下示例1:

from tenacity import *

@retry

def never_give_up():

print("Retry forever ignoring Exceptions")

raise Exception

never_give_up()

可以看到tenacity是通过给函数添加retry装饰器实现原函数的自动重试功能的,上述代码只要执行never_give_up函数时抛异常,就会一直重尝试,直到成功,那么如果我只想重尝试几次,看如下示例2:

@retry(stop=stop_after_attempt(7))

def stop_after_7_attempts():

print("Stopping after 7 attempts")

raise Exception

是不是非常简单?tenacity功能还是比较全面的,除了上述简单的重试操作外,还支持有间隔时间的重试、指定异常类型的重试、协程对象的重试等功能,详细使用方法见github: https://github.com/jd/tenacity。

tenacity源码分析

tenacity源代码的质量还是非常高的,那么接下来我们来分析一下tenacity的源代码,看看它是如何实现的。

我这里的分析会从最初的v1.0.0版本开始分析,分析它一步步演进的过程,有时候上来直接分析最新版本会有些蒙圈,从早期的版本开始往后看会好很多。

v1.0.0版本主要的实现代码就在retrying一个文件中。

从retry这个装饰器入手,源代码如下:

def retry(*dargs, **dkw):

def wrap(f):

def wrapped_f(*args, **kw):

return Retrying(*dargs, **dkw).call(f, *args, **kw)

return wrapped_f

def wrap_simple(f):

def wrapped_f(*args, **kw):

return Retrying().call(f, *args, **kw)

return wrapped_f

if len(dargs) == 1 and callable(dargs[0]):

return wrap_simple(dargs[0])

else:

return wrap我们先看函数加上retry装饰器后会发生什么

最开始的示例1代码中,我们给never_give_up加上了retry装饰器,效果等同于never_give_up = retry(never_give_up),相当于把自己当作参数传入了retry中。

retry装饰器会根据传入的参数来决定做什么操作,可以看到never_give_up是唯一的一个位置参数,且是可调用的,所以满足len(dargs) == 1 and callable(dargs[0]),所以会执行wrap_simple(dargs[0]),传入的参数就是never_give_up,然后返回wrap_simple的内部函数wrapped_f,实现never_give_up的闭包,此时装饰结束。

再来看看示例2代码,这次retry是带参数的装饰器,装饰前会先调用retry,也就是先执行retry(stop=stop_after_attempt(7)),这时传入的是键值对参数,不满足len(dargs) == 1 and callable(dargs[0]),所以返回retry的内部函数wrap,可以看到wrap才是真正的装饰函数,之后正式进入装饰过程, 执行never_give_up= wrap(never_give_up),返回wrap的内部函数wrapped_f,实现never_give_up的闭包,此时装饰结束。

综上,retry依靠对传入参数的判断,实现了同时支持带参数和不带参数的装饰器。

2. 接着来看tenacity的核心类Retrying

当我们的函数被retry装饰器装饰后,再调用函数时会先实例化一个Retrying对象,然后执行Retrying的call方法,传入call方法的参数就是我们的原始函数和参数,也就是说实现重试的核心代码就在call方法中了。

先看看Retrying的构造方法,看它都做了哪些初始化:

class Retrying:

def __init__(self,

stop='never_stop',

stop_max_attempt_number=5,

stop_max_delay=100,

wait='no_sleep',

wait_fixed=1000,

wait_random_min=0, wait_random_max=1000,

wait_incrementing_start=0, wait_incrementing_increment=100,

wait_exponential_multiplier=1, wait_exponential_max=MAX_WAIT,

retry_on_exception=None,

retry_on_result=None,

wrap_exception=False):

self.stop = getattr(self, stop)

self._stop_max_attempt_number = stop_max_attempt_number

self._stop_max_delay = stop_max_delay

self.wait = getattr(self, wait)

self._wait_fixed = wait_fixed

self._wait_random_min = wait_random_min

self._wait_random_max = wait_random_max

self._wait_incrementing_start = wait_incrementing_start

self._wait_incrementing_increment = wait_incrementing_increment

self._wait_exponential_multiplier = wait_exponential_multiplier

self._wait_exponential_max = wait_exponential_max

if retry_on_exception is None:

self._retry_on_exception = self.always_reject

else:

self._retry_on_exception = retry_on_exception

if retry_on_result is None:

self._retry_on_result = self.never_reject

else:

self._retry_on_result = retry_on_result

self._wrap_exception = wrap_exception

Retrying初始化时,定义了重试条件、重试次数、重试间隔等信息。还可以看到有些属性是依靠反射映射到对应的实例方法的,如stop、wait。

再来看看核心的call方法代码:

def call(self, fn, *args, **kwargs):

start_time = int(round(time.time() * 1000))

attempt_number = 1

while True:

try:

attempt = Attempt(fn(*args, **kwargs), attempt_number, False)

except BaseException as e:

attempt = Attempt(e, attempt_number, True)

if not self.should_reject(attempt):

return attempt.get(self._wrap_exception)

delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time

if self.stop(attempt_number, delay_since_first_attempt_ms):

raise RetryError(attempt)

else:

sleep = self.wait(attempt_number, delay_since_first_attempt_ms)

time.sleep(sleep / 1000.0)

attempt_number += 1

call方法中有个while循环,可见满足某种条件就会进行重试。

先假设调用fn函数(被retry装饰的函数)不会报错,这时fn的结果会封装到Attempt里,之后调用should_reject方法,代码如下:

def should_reject(self, attempt):

reject = False

if attempt.has_exception:

reject |= self._retry_on_exception(attempt.value)

else:

reject |= self._retry_on_result(attempt.value)

return reject

这个函数主要是用来实现根据报错类型或结果来决定是否重试。

假设我们没有设置这些选项,那么shold_reject会返回False,那么就会执行attempt对象的get方法:

def get(self, wrap_exception=False):

"""Return the return value of this Attempt instance or raise an Exception.If wrap_exception is true, this Attempt is wrapped inside of aRetryError before being raised."""

if self.has_exception:

if wrap_exception:

raise RetryError(self)

else:

raise self.value

else:

return self.value

has_exception在Attempt初始化时设的是False,所以最终会返回fn函数的执行结果。

上面是fn函数不报错的情况,如果报错,则就是另一条路线,代码会走到调用self.stop这里,根据重试次数和策略决定是继续循环还是抛出异常。

至此,第一个版本的核心部分分析完了,还是非常简单的,而且后面的版本核心思路其实都和第一个版本差不多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值