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这里,根据重试次数和策略决定是继续循环还是抛出异常。

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

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

  1. 【报错】flink源码分析: has no more allocated slots与思考

    文章目录 一. 任务描述与一句话 1. 任务描述 2. 一句话 二. 日志分析 1. 申请一个task manager 2. 大概3分钟后运行这个tm时,报资源找不到 三. 源码分析与报错机制定位 1 ...

  2. 404页面模板php,Thinkphp5老司机网站报错404页面模板源码

    来,来,来,学习下怎么做前端页面,分享个老司机网站报错404页面模板源码, 另外给提个建议,最好是有个地方可以上传源码文件的,这样子格式就不会有什么问题了.一般复制出去大家最好都用UTF-8格式. H ...

  3. 分享个老司机网站报错404页面模板源码

    版权声明:转载请注明原创地址 https://blog.csdn.net/u013032788/article/details/80623193 来,来,来,要不要学习下怎么做前端页面,分享个老司机网 ...

  4. oracle匿名代码块执行insert,MyBatis+Oracle在执行insert时空值报错之从源码寻找解决办法...

    mybatis-oracle-config.xml 复制代码 1 <?xml version="1.0" encoding="UTF-8"?> 2 ...

  5. centos7源码安装mysql报错_CentOS7 下源码安装MySQL数据库 8.0.11

    本文主要向大家介绍了CentOS7 下源码安装MySQL数据库 8.0.11,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助. CentOS7 下源码安装MySQL 8.0.11 系 ...

  6. python2.7安装报错_python2.7源码安装方式

    安装python2.7 下载Python 2.7, 下载地址 解压安装 tar -xzvf Python-2.7.15.tgz cd Python-2.7.15 ./configure --prefi ...

  7. pip安装wxpython报错traceback_使用源码编译wxpython-基于python2.7

    1.前言 本文主要讲述在linux环境下进行编译wxpython,在windows下面安装wxpython很简单,只要下载,然后直接执行exe文件,下一步下一步即可安装,在linux下面,则具有很多步 ...

  8. centos7源码安装mysql报错_centos7.3源码安装mysql

    环境说明: 该系统第一次安装mysql. 自己指定安装目录,指定数据文件目录. linux系统版本:CentOS 7.3 64位 安装源文件版本:mysql-5.7版本 mysql安装位置:/soft ...

  9. Linux内核源码分析--内核启动之(1)zImage自解压过程(Linux-3.0 ARMv7) 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-4938388.html   研究内核源码和内核运行原理的时候,很总要的一点是要了解内核的初始情况,也就是要了 ...

  10. 精尽Spring MVC源码分析 - 一个请求的旅行过程

    我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后 ...

最新文章

  1. idea 连接 mysql_IDEA 与MySQL连接问题
  2. Lync与Exchange 2013 UM集成配置
  3. HTML学习感想(4)【密码输入框、单选、复选框】
  4. 服务器2003添加共享文档权限,Windows2003使用命令行设置共享权限与安全权限心得...
  5. 阿里云mysql安装
  6. article之api文档
  7. keil stm32标准库放在哪里_STM32之PWM
  8. ggplot2中显示坐标轴_ggplot2作图:修改图中一切文本的外观
  9. jetty java heap space_JFinal + HTTL + jdk1.7 启动服务内存溢出,Java heap space 但jdk1.6正常...
  10. Python-----包和日志的使用
  11. 在磁盘上给文件快速预留一大片空间
  12. 一点Python学习资源
  13. 通过SessionID和用户名来保证同一个用户不能同时登录
  14. 2019 CCF 推荐 中文期刊
  15. Android主备域名切换实施方案(Ping工具Demo)
  16. html语言标题怎么居中,html标题栏代码 HTML中怎么设置标题居中
  17. C++: STL: atomic
  18. word2vec加载异常解决:UnicodeDecodeError: ‘utf-8‘ codec can‘t decode bytes in position。。。
  19. android n ify三星,三星年度Android旗舰Galaxy S9包装盒曝光!
  20. 中国科学院大学重庆学院继续教育学院学前教育专业

热门文章

  1. 【Day42 文献精读】A Bayesian Model of Perceived Head-Centered Velocity during Smooth Pursuit Eye Movement
  2. 全智通A+常见问题汇总解答—A+配件仓库—维修领料—编辑领料单:最后一个仓库无法显示
  3. sPortfolio: Stratified Visual Analysis of Stock Portfolios
  4. DHCP 协议(一)
  5. 黑苹果系统完全移植新硬盘
  6. ubuntu 安装chrome并禁止提示更新
  7. php文件zend解密,php zend无法解密怎么办
  8. HANA 存储过程 YTD
  9. 【C语言】编写一个函数判别某一数是否为素数,若是,返回值为1;否则,返回值为0。
  10. WPS2007去广告