python重试库retryiny源码剖析
上篇博文介绍了常见需要进行请求重试的场景,本篇博文试着剖析有名的python第三方库retrying源码。
在剖析其源码之前,有必要讲一下retrying的用法,方便理解。
安装:
pip install retrying
或者
easy_install retrying
一些用法实例如下:
#example 1 from retrying import retry@retry def never_give_up_never_surrender():print "一直重试且两次重试之间无需等待"
#example 2 from retrying import retry@retry(stop_max_attempt_number=7) def stop_after_7_attempts():print "重试七次后停止"
#example 3 from retrying import retry@retry(stop_max_delay=10000) def stop_after_10_s():print "十秒之后停止重试"
#example 4 from retrying import retry@retry(wait_fixed=2000) def wait_2_s():print "每次重试间隔两秒"
#example 5 from retrying import retry@retry(wait_random_min=1000, wait_random_max=2000) def wait_random_1_to_2_s():print "每次重试随机等待1到2秒"
#example 6 from retrying import retry@retry(wait_exponential_multiplier=1000, wait_exponential_max=10000) def wait_exponential_1000():print "指数退避,每次重试等待 2^x * 1000 毫秒,上限是10秒,达到上限后每次都等待10秒"
#example 7 def retry_if_io_error(exception):"""Return True if we should retry (in this case when it's an IOError), False otherwise"""return isinstance(exception, IOError)@retry(retry_on_exception=retry_if_io_error) def might_io_error():print "IO异常则重试,并且将其它异常抛出"@retry(retry_on_exception=retry_if_io_error, wrap_exception=True) def only_raise_retry_error_when_not_io_error():print "IO异常则重试,并且将其它异常用RetryError对象包裹"
#exampe 8,根据返回结果判断是否重试 def retry_if_result_none(result):"""Return True if we should retry (in this case when result is None), False otherwise"""return result is None@retry(retry_on_result=retry_if_result_none) def might_return_none():print "若返回结果为None则重试"
上面八个例子是retrying的用法,只需在要重试的方法上加上@retry注解,并以相应的条件为参数即可,那么@retry背后到底是如何实现的呢?下面给出@retry注解实现的方法。
1 #装饰器模式,对需要重试的函数,利用retry注解返回 2 def retry(*dargs, **dkw): 3 """ 4 Decorator function that instantiates the Retrying object 5 @param *dargs: positional arguments passed to Retrying object 6 @param **dkw: keyword arguments passed to the Retrying object 7 """ 8 # support both @retry and @retry() as valid syntax 9 #当用法为@retry不带括号时走这条路径,dargs[0]为retry注解的函数,返回函数对象wrapped_f 10 if len(dargs) == 1 and callable(dargs[0]): 11 def wrap_simple(f): 12 13 @six.wraps(f)#注解用于将函数f的签名复制到新函数wrapped_f 14 def wrapped_f(*args, **kw): 15 return Retrying().call(f, *args, **kw) 16 17 return wrapped_f 18 19 return wrap_simple(dargs[0]) 20 21 else:#当用法为@retry()带括号时走这条路径,返回函数对象wrapped_f 22 def wrap(f): 23 24 @six.wraps(f)#注解用于将函数f的签名复制到新函数wrapped_f 25 def wrapped_f(*args, **kw): 26 return Retrying(*dargs, **dkw).call(f, *args, **kw) 27 28 return wrapped_f 29 30 return wrap
当用@retry标记函数时,例如实例1,其实执行了
never_give_up_never_surrender = retry(never_give_up_never_surrender)
此时的never_give_up_never_surrender函数实际上是10-19行返回的wrapped_f函数,后续对never_give_up_never_surrender函数的调用都是调用的14行的wrapped_f函数。
当使用@retry()或者带参数的@retry(params)时,如实例2,实际执行了:
stop_after_7_attempts = retry(stop_max_attempt_number)(stop_after_7_attempts)
此时的stop_after_7_attempts函数实际上是22-29行的wrapped_f函数,后续对stop_after_7_attempts函数的调用都是对25行的wrapped_f函数调用。
可以看到实际上@retry将对需要重试的函数调用转化为对Retrying类中call函数的调用,重试逻辑也在这个函数实现,实现对逻辑代码的无侵入,代码如下:
1 def call(self, fn, *args, **kwargs): 2 start_time = int(round(time.time() * 1000)) 3 attempt_number = 1 4 while True: 5 #_before_attempts为@retry传进来的before_attempts,在每次调用函数前执行一些操作 6 if self._before_attempts: 7 self._before_attempts(attempt_number) 8 9 try:#Attempt将函数执行结果或者异常信息以及执行次数作为内部状态,用True或False标记是内部存的值正常执行结果还是异常 10 attempt = Attempt(fn(*args, **kwargs), attempt_number, False) 11 except: 12 tb = sys.exc_info()#获取异常堆栈信息,sys.exc_info()返回type(异常类型), value(异常说明), traceback(traceback对象,包含更丰富的信息) 13 attempt = Attempt(tb, attempt_number, True) 14 15 if not self.should_reject(attempt):#根据本次执行结果或异常类型判断是否应该停止 16 return attempt.get(self._wrap_exception) 17 18 if self._after_attempts:#_after_attempts为@retry传进来的after_attempts,在每次调用函数后执行一些操作 19 self._after_attempts(attempt_number) 20 21 delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time 22 if self.stop(attempt_number, delay_since_first_attempt_ms):#根据重试次数和延迟判断是否应该停止 23 if not self._wrap_exception and attempt.has_exception: 24 # get() on an attempt with an exception should cause it to be raised, but raise just in case 25 raise attempt.get() 26 else: 27 raise RetryError(attempt) 28 else:#不停止则等待一定时间,延迟时间根据wait函数返回值和_wait_jitter_max计算 29 sleep = self.wait(attempt_number, delay_since_first_attempt_ms) 30 if self._wait_jitter_max: 31 jitter = random.random() * self._wait_jitter_max 32 sleep = sleep + max(0, jitter) 33 time.sleep(sleep / 1000.0) 34 35 attempt_number += 1 #进行下一轮重试
9-13行将函数执行返回结果或异常存入Attempt对象attempt中,Attempt类如下:
class Attempt(object):"""An Attempt encapsulates a call to a target function that may end as anormal return value from the function or an Exception depending on whatoccurred during the execution."""#value值为函数返回结果或异常,根据has_exception判断def __init__(self, value, attempt_number, has_exception):self.value = valueself.attempt_number = attempt_numberself.has_exception = has_exception#返回函数执行结果或异常,并根据wrap_exception参数对异常用RetryError包裹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:#重新构造原异常抛出six.reraise(self.value[0], self.value[1], self.value[2])else:return self.valuedef __repr__(self):if self.has_exception:return "Attempts: {0}, Error:\n{1}".format(self.attempt_number, "".join(traceback.format_tb(self.value[2])))else:return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value)
15行根据should_reject函数的返回值判断是否停止重试,代码如下:
def should_reject(self, attempt):reject = False#假如异常在retry_on_exception参数中返回True,则重试,默认不传异常参数时,发生异常一直重试if attempt.has_exception:reject |= self._retry_on_exception(attempt.value[1])else:#假如函数返回结果在retry_on_result参数函数中为True,则重试reject |= self._retry_on_result(attempt.value) return reject
22行根据重试次数和延迟判断是否应该停止重试,self.stop的赋值代码在构造函数中,代码片段如下:
stop_funcs = []if stop_max_attempt_number is not None:stop_funcs.append(self.stop_after_attempt)if stop_max_delay is not None:stop_funcs.append(self.stop_after_delay)if stop_func is not None:self.stop = stop_funcelif stop is None:#执行次数和延迟任何一个达到限制则停止self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs)else:self.stop = getattr(self, stop)
def stop_after_attempt(self, previous_attempt_number, delay_since_first_attempt_ms):"""Stop after the previous attempt >= stop_max_attempt_number."""return previous_attempt_number >= self._stop_max_attempt_numberdef stop_after_delay(self, previous_attempt_number, delay_since_first_attempt_ms):"""Stop after the time from the first attempt >= stop_max_delay."""return delay_since_first_attempt_ms >= self._stop_max_delay
29-33行等待一段时间再次重试,其中延迟时间重点是根据29行的wait函数计算,wait函数在构造函数中赋值,代码片段如下:
wait_funcs = [lambda *args, **kwargs: 0]if wait_fixed is not None:wait_funcs.append(self.fixed_sleep)if wait_random_min is not None or wait_random_max is not None:wait_funcs.append(self.random_sleep)if wait_incrementing_start is not None or wait_incrementing_increment is not None:wait_funcs.append(self.incrementing_sleep)if wait_exponential_multiplier is not None or wait_exponential_max is not None:wait_funcs.append(self.exponential_sleep)if wait_func is not None:self.wait = wait_funcelif wait is None:#返回几个函数的最大值,作为等待时间self.wait = lambda attempts, delay: max(f(attempts, delay) for f in wait_funcs)else:self.wait = getattr(self, wait)
其中最值得研究的是指数退避延迟时间计算方法,函数为exponential_sleep,代码如下:
def exponential_sleep(self, previous_attempt_number, delay_since_first_attempt_ms):exp = 2 ** previous_attempt_number result = self._wait_exponential_multiplier * exp #延迟时间为_wait_exponential_multiplier*2^xif result > self._wait_exponential_max:#假如大于退避上限_wait_exponential_max,则result为上限值result = self._wait_exponential_maxif result < 0:result = 0return result
转载于:https://www.cnblogs.com/killianxu/p/9807955.html
python重试库retryiny源码剖析相关推荐
- print python 带回车_python标准库threading源码解读【二】
紧接着上一篇文章继续解析源码 甘蔗:python标准库threading源码解读[一]zhuanlan.zhihu.com 目录 Event的介绍和用法 Event源码解析 以后的内容尽量少一点并且 ...
- 一个完整的python项目源码-一个Python开源项目-腾讯哈勃沙箱源码剖析(上)
前言 2019年来了,2020年还会远吗? 请把下一年的年终奖发一下,谢谢... 回顾逝去的2018年,最大的改变是从一名学生变成了一位工作者,不敢说自己多么的职业化,但是正在努力往那个方向走. 以前 ...
- Python源码剖析[16] —— Pyc文件解析
Python源码剖析[16] -- Pyc文件解析 2008-02-28 18:29:55| 分类: Python |举报 |字号 订阅 Python源码剖析 --Pyc文件解析 本文作者: Rob ...
- python的对象模型_[ Python 源码剖析] 对象模型概述
Python 是一门 面向对象 语言,实现了一个完整的面向对象体系,简洁而优雅. 与其他面向对象编程语言相比, Python 有自己独特的一面. 这让很多开发人员在学习 Python 时,多少有些无所 ...
- Python envoy 模块源码剖析
Kenneth Reitz 是公认的这个世界上 Python 代码写得最好的人之一.抱着学习的心态,我阅读了 Reitz 写的 envoy 模块的源码,将笔记记录如下. 介绍 和 requests 模 ...
- 《Python 源码剖析》一些理解以及勘误笔记(3)
以下是本人阅读此书时理解的一些笔记,包含一些影响文义的笔误修正,当然不一定正确,贴出来一起讨论. 注:此书剖析的源码是2.5版本,在python.org 可以找到源码.纸质书阅读,pdf 贴图. 文章 ...
- Python源码剖析[1] —— 编译Python
[ 绝对原创,转载请注明出处] 注意 :第一部分Python总体架构采用了网络文档<The Architecture of Python>,这是网络上唯一可见的以剖析Python实现为己任 ...
- 《Python源码剖析》读书笔记
<Python源码剖析>电子书下载 http://download.csdn.net/detail/xiarendeniao/5130403 Python源码在官网有下载链接,用ctags ...
- python解释器源码 pdf_《python解释器源码剖析》第0章--python的架构与编译python
本系列是以陈儒先生的<python源码剖析>为学习素材,所总结的笔记.不同的是陈儒先生的<python源码剖析>所剖析的是python2.5,本系列对应的是python3.7. ...
- Python源码剖析[19] —— 执行引擎之一般表达式(2)
Python源码剖析 --Python执行引擎之一般表达式(2) 本文作者: Robert Chen(search.pythoner@gmail.com ) 3.2 Simple.py 前面我 ...
最新文章
- SpringBoot (五) :SpringBoot整合mybatis
- tensorflow算法实战:普通的数据训练和迁移学习之后的数据训练进行图像的识别(包括前端页面)
- cisco 交换机镜像
- 撸了今年阿里、网易和美团的面试,我有一个重要发现.......
- python标识运算符_讲解Python中的标识运算符
- html图片在ie中有边框,html – 表格的边框在IE中不起作用
- 安装python37路径报错_解决pycharm安装python库报错问题
- 第二轮冲刺-Runner站立会议06
- 下了班----你干啥
- python中list的切片和range函数
- [Ext JS6]Microloader - 微加载器
- C# string.Format json格式字符串报错”输入字符串的格式不正确“
- 软件测试初学者如何编写Jmeter测试脚本?
- html字体重叠的原因,PPT输入文字的时候字重叠是怎么回事呢?
- 5款最好的安卓界面设计工具推荐
- 麋鹿分布图制作(二)—— 用Python和R在地图上打点
- MacBook Pro 15寸 Late 2013 更换2TB NVME硬盘利用Big Sur完美修复睡眠唤醒及开机时长问题
- Ubuntu18.04 flash插件安装
- 如何定制B2C电商网站
- Fitzpatrick Manufacturing部署 Sawyer智能协作机器人
热门文章
- 数据分析的升级版本--excel数据对比--代码实现
- 凯撒密码的实践使用-2--加密,解密的python代码事项
- CASE WHEN 及 SELECT CASE WHEN的用法
- python中的request库_Python中的Requests库简要总结
- android模拟器 bridge,ADB (Android Debug Bridge)简介
- vue项目模板_VSCode( VisualStudioCode) 写vue项目一键生成.vue模版修改定义其他模板
- [HDU4352]XHXJ's LIS
- 链接数据库 并且进行查询操作
- 使用锚标记返回网页顶部的方法
- ACdream 1417 Numbers