Python重试库Retrying和Tenacity的实现

作者:

文章目录
  • Retrying 是一个通用重试库,用于简化任何需要重试的任务,已不再维护,功能: 通用装饰器 指定停止条件,如重试次数 指定等待条件,如重试间的指数回退休眠 自定义异常重试 自定义重试预期返回的结果 Tenacity 是上述库的分支,修复了一些BUG,增加了新功能: 异步协程重试 上下文管理器重试 组合停止条件 推荐使用 Tenacity
  • pip install retrying pip install tenacity
  • 目录
    • 简介
    • 安装
    • Retrying
      • 初试
      • 最大重试次数
      • 最大重试时间
      • 重试间隔
      • 随机间隔
      • 指数级重试间隔
      • 自定义异常重试
      • 自定义返回重试
      • 参数
    • Tenacity
      • 初试
      • 最大重试次数
      • 最大重试时间
      • 组合停止条件
      • 重试间隔
      • 随机间隔
      • 指数级重试间隔
      • 重试间隔链
      • 自定义异常重试
      • 自定义返回重试
      • 显式重试
      • 重新捕获异常
      • 重试前记日志
      • 重试后记日志
      • 重试失败后记日志
      • 重试统计
      • 自定义回调函数
      • 上下文管理器
      • 支持异步和协程
      • 参数

    Retrying 是一个通用重试库,用于简化任何需要重试的任务,已不再维护,功能:

    • 通用装饰器
    • 指定停止条件,如重试次数
    • 指定等待条件,如重试间的指数回退休眠
    • 自定义异常重试
    • 自定义重试预期返回的结果

    Tenacity 是上述库的分支,修复了一些BUG,增加了新功能:

    • 异步协程重试
    • 上下文管理器重试
    • 组合停止条件

    推荐使用 Tenacity

    pip install retrying
    pip install tenacity
    

    重试无数次直至成功

    import random
    from retrying import retry
    
    n = 0  # 记录重试次数
    
    
    @retry
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            raise Exception
        else:
            print(f'tried {n} times')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        # tried 1 times
        # tried 2 times
        # tried 4 times
        # tried 5 times
        # tried 13 times
    

    import random
    from retrying import retry
    
    n = 0  # 记录重试次数
    
    
    @retry(stop_max_attempt_number=3)  # 最大重试次数
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            raise Exception
        else:
            print(f'tried {n} times and success.')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        try:
            do_something_unreliable()
        except Exception as e:
            print(f'tried {n} times but failed.')
            n = 0  # 重置重试次数
        # tried 3 times but failed.
        # tried 1 times and success.
        # tried 0 times and success.
        # tried 2 times and success.
        # tried 3 times but failed.
    

    单位为毫秒

    import time
    from retrying import retry
    
    n = 0  # 记录重试次数
    
    
    @retry(stop_max_delay=3000)  # 最大重试时间为3s
    def do_something_unreliable():
        global n
        n += 1
        raise Exception
    
    
    start = time.time()
    try:
        do_something_unreliable()
    except:
        pass
    end = time.time()
    print(f'tried {n} times, pass {end - start} seconds')
    # tried 796060 times, pass 2.999951124191284 seconds
    

    import time
    import random
    from retrying import retry
    
    n = 0  # 记录重试次数
    start = time.time()  # 开始时间
    
    
    @retry(wait_fixed=500)  # 重试间隔0.5秒
    def do_something_unreliable():
        global n, start
        if random.randint(0, 3) > 1:
            n += 1
            raise Exception
        else:
            end = time.time()
            print(f'tried {n} times, pass {end - start} seconds')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        start = time.time()  # 更新开始时间
        # tried 0 times, pass 0.0 seconds
        # tried 3 times, pass 1.538625955581665 seconds
        # tried 1 times, pass 0.5115864276885986 seconds
        # tried 1 times, pass 0.5024125576019287 seconds
        # tried 0 times, pass 0.0 seconds
    

    import time
    import random
    from retrying import retry
    
    n = 0  # 记录重试次数
    start = time.time()  # 开始时间
    
    
    @retry(wait_random_min=500, wait_random_max=1000)  # 重试间隔0.5-1秒
    def do_something_unreliable():
        global n, start
        if random.randint(0, 3) > 1:
            n += 1
            raise Exception
        else:
            end = time.time()
            print(f'tried {n} times, pass {end - start} seconds')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        start = time.time()  # 更新开始时间
        # tried 1 times, pass 0.7865383625030518 seconds
        # tried 1 times, pass 0.5917379856109619 seconds
        # tried 6 times, pass 4.129276990890503 seconds
        # tried 0 times, pass 0.0 seconds
        # tried 3 times, pass 2.2903735637664795 seconds
    

    x为重试次数,如第一次重试间隔2s,第二次4s,第三次8s,第四次16s(不设封顶的话)

    import time
    import random
    from retrying import retry
    
    n = 0  # 记录重试次数
    start = time.time()  # 开始时间
    
    
    @retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)  # 指数级重试间隔=2^x*1000ms,10s封顶
    def do_something_unreliable():
        global n, start
        if random.randint(0, 3) > 1:
            n += 1
            raise Exception
        else:
            end = time.time()
            print(f'tried {n} times, pass {end - start} seconds')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        start = time.time()  # 更新开始时间
        # tried 0 times, pass 0.0 seconds
        # tried 1 times, pass 2.0003767013549805 seconds
        # tried 0 times, pass 0.0 seconds
        # tried 0 times, pass 0.0 seconds
        # tried 4 times, pass 24.02167558670044 seconds
    

    只重试 IOError

    import random
    from retrying import retry
    
    n = 0  # 记录重试次数
    
    
    @retry(retry_on_exception=lambda x: isinstance(x, IOError))  # 自定义异常重试
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            raise IOError
        else:
            print(f'tried {n} times')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        # tried 5 times
        # tried 2 times
        # tried 3 times
        # tried 6 times
        # tried 2 times
    

    只重试 IOError,其余抛出

    import random
    from retrying import retry
    
    n = 0  # 记录重试次数
    
    
    @retry(retry_on_exception=lambda x: isinstance(x, IOError), wrap_exception=True)  # 自定义异常结果
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            raise IOError
        else:
            print(f'tried {n} times')
            n = 0  # 重置重试次数
        if random.randint(0, 2) > 1:
            raise IndexError
    
    
    for i in range(5):
        try:
            do_something_unreliable()
        except Exception as e:
            print(e)
        # tried 4 times
        # tried 5 times
        # tried 2 times
        # tried 6 times
        # tried 1 times
        # RetryError[Attempts: 2, Error:
        #   File "C:UsersAdministratorEnvstestlibsite-packagesretrying.py", line 200, in call
        #     attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
        #   File "D:/mycode/xxx.py", line 18, in do_something_unreliable
        #     raise IndexError
        # ]
    

    返回为 None 则重试

    import random
    from retrying import retry
    
    n = 0  # 记录重试次数
    
    
    @retry(retry_on_result=lambda x: x is None)  # 自定义异常重试
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            return None
        else:
            return n
    
    
    for i in range(5):
        print(f'tried {do_something_unreliable()} times')
        n = 0  # 重置重试次数
        # tried 10 times
        # tried 8 times
        # tried 0 times
        # tried 10 times
        # tried 3 times
    

    参数 功能
    重试直至成功
    stop_max_attempt_number 最大重试次数
    stop_max_delay 最大重试时间(毫秒)
    wait_fixed 重试间隔(毫秒)
    wait_random_min 和 wait_random_max 随机重试间隔(毫秒)
    wait_exponential_multiplier 和 wait_exponential_max 指数级重试间隔(毫秒)
    retry_on_exception 自定义异常重试
    wrap_exception 是否抛出其余重试
    retry_on_result 自定义异常结果

    import random
    from tenacity import retry
    
    n = 0  # 记录重试次数
    
    
    @retry
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            raise Exception
        else:
            print(f'tried {n} times')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        # tried 1 times
        # tried 0 times
        # tried 4 times
        # tried 7 times
        # tried 3 times
    

    import random
    from tenacity import retry, stop_after_attempt
    
    n = 0  # 记录重试次数
    
    
    @retry(stop=stop_after_attempt(3))  # 最大重试次数
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            raise Exception
        else:
            print(f'tried {n} times and success.')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        try:
            do_something_unreliable()
        except Exception as e:
            print(f'tried {n} times but failed.')
            n = 0  # 重置重试次数
        # tried 1 times and success.
        # tried 3 times but failed.
        # tried 3 times but failed.
        # tried 2 times and success.
        # tried 2 times and success.
    

    单位为秒

    import time
    from tenacity import retry, stop_after_delay
    
    n = 0  # 记录重试次数
    
    
    @retry(stop=stop_after_delay(3))  # 最大重试时间为3s
    def do_something_unreliable():
        global n
        n += 1
        raise Exception
    
    
    start = time.time()
    try:
        do_something_unreliable()
    except:
        pass
    end = time.time()
    print(f'tried {n} times, pass {end - start} seconds')
    # tried 206456 times, pass 2.9916257858276367 seconds
    

    通过操作符 | 组合多个停止条件

    import time
    from tenacity import retry, stop_after_attempt, stop_after_delay
    
    n = 0  # 记录重试次数
    
    
    @retry(stop=stop_after_attempt(300000) | stop_after_delay(3))  # 最大重试30w次 或 最长重试3秒
    def do_something_unreliable():
        global n
        n += 1
        raise Exception
    
    
    start = time.time()
    try:
        do_something_unreliable()
    except:
        pass
    end = time.time()
    print(f'tried {n} times, pass {end - start} seconds')
    # tried 206456 times, pass 2.9916257858276367 seconds
    

    import time
    import random
    from tenacity import retry, wait_fixed
    
    n = 0  # 记录重试次数
    start = time.time()  # 开始时间
    
    
    @retry(wait=wait_fixed(0.5))  # 重试间隔0.5秒
    def do_something_unreliable():
        global n, start
        if random.randint(0, 3) > 1:
            n += 1
            raise Exception
        else:
            end = time.time()
            print(f'tried {n} times, pass {end - start} seconds')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        start = time.time()  # 更新开始时间
        # tried 1 times, pass 0.5027601718902588 seconds
        # tried 2 times, pass 1.0299296379089355 seconds
        # tried 0 times, pass 0.0 seconds
        # tried 3 times, pass 1.5316619873046875 seconds
        # tried 4 times, pass 2.0474536418914795 seconds
    

    import time
    import random
    from tenacity import retry, wait_random
    
    n = 0  # 记录重试次数
    start = time.time()  # 开始时间
    
    
    @retry(wait=wait_random(min=0.5, max=1))  # 重试间隔0.5-1秒
    def do_something_unreliable():
        global n, start
        if random.randint(0, 3) > 1:
            n += 1
            raise Exception
        else:
            end = time.time()
            print(f'tried {n} times, pass {end - start} seconds')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        start = time.time()  # 更新开始时间
        # tried 3 times, pass 2.287365674972534 seconds
        # tried 0 times, pass 0.0 seconds
        # tried 2 times, pass 1.4969894886016846 seconds
        # tried 0 times, pass 0.0 seconds
        # tried 6 times, pass 4.51520299911499 seconds
    

    同样可以组合 

    import time
    import random
    from tenacity import retry, wait_fixed, wait_random
    
    n = 0  # 记录重试次数
    start = time.time()  # 开始时间
    
    
    @retry(wait=wait_fixed(0.5) + wait_random(min=0.5, max=1))  # 重试间隔1-1.5秒
    def do_something_unreliable():
        global n, start
        if random.randint(0, 3) > 1:
            n += 1
            raise Exception
        else:
            end = time.time()
            print(f'tried {n} times, pass {end - start} seconds')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        start = time.time()  # 更新开始时间
        # tried 2 times, pass 2.9294729232788086 seconds
        # tried 0 times, pass 0.0 seconds
        # tried 0 times, pass 0.0 seconds
        # tried 3 times, pass 3.8608667850494385 seconds
        # tried 1 times, pass 1.4092319011688232 seconds
    

    x为重试次数,最小1s,最大10s

    import time
    import random
    from tenacity import retry, wait_exponential
    
    n = 0  # 记录重试次数
    start = time.time()  # 开始时间
    
    
    @retry(wait=wait_exponential(multiplier=1, min=1, max=10))  # 指数级重试间隔=2^x*1s,最小1s,最大10s
    def do_something_unreliable():
        global n, start
        if random.randint(0, 3) > 1:
            n += 1
            raise Exception
        else:
            end = time.time()
            print(f'tried {n} times, pass {end - start} seconds')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        start = time.time()  # 更新开始时间
        # tried 0 times, pass 0.0 seconds
        # tried 6 times, pass 35.06491994857788 seconds
        # tried 2 times, pass 3.013124942779541 seconds
        # tried 1 times, pass 1.010573387145996 seconds
        # tried 0 times, pass 0.0 seconds
    

    import time
    import random
    from tenacity import retry, wait_fixed, wait_chain
    
    n = 0  # 记录重试次数
    start = time.time()  # 开始时间
    
    
    @retry(wait=wait_chain(*[wait_fixed(0.5)] * 3 + [wait_fixed(1)] * 2 + [wait_fixed(2)]))  # 重试间隔链,先3个0.5秒,再2个1秒,之后都是2秒
    def do_something_unreliable():
        global n
        if n == 10:
            return
        n += 1
        end = time.time()
        print(f'tried {n} times, pass {end - start} seconds')
        raise Exception
    
    
    do_something_unreliable()
    # tried 1 times, pass 0.0 seconds
    # tried 2 times, pass 0.5056586265563965 seconds
    # tried 3 times, pass 1.0193772315979004 seconds
    # tried 4 times, pass 1.5333683490753174 seconds
    # tried 5 times, pass 2.5386297702789307 seconds
    # tried 6 times, pass 3.5489938259124756 seconds
    # tried 7 times, pass 5.551833629608154 seconds
    # tried 8 times, pass 7.559761047363281 seconds
    # tried 9 times, pass 9.561469554901123 seconds
    # tried 10 times, pass 11.570155143737793 seconds
    

    只重试 IOError

    import random
    from tenacity import retry, retry_if_exception_type
    
    n = 0  # 记录重试次数
    
    
    @retry(retry=retry_if_exception_type(IOError))  # 自定义异常重试
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            raise IOError
        else:
            print(f'tried {n} times')
            n = 0  # 重置重试次数
    
    
    for i in range(5):
        do_something_unreliable()
        # tried 5 times
        # tried 2 times
        # tried 3 times
        # tried 6 times
        # tried 2 times
    

    返回为 None 则重试

    import random
    from tenacity import retry, retry_if_result
    
    n = 0  # 记录重试次数
    
    
    @retry(retry=retry_if_result(lambda x: x is None))  # 自定义异常结果
    def do_something_unreliable():
        global n
        if random.randint(0, 10) > 1:
            n += 1
            return None
        else:
            return n
    
    
    for i in range(5):
        print(f'tried {do_something_unreliable()} times')
        n = 0  # 重置重试次数
        # tried 10 times
        # tried 8 times
        # tried 0 times
        # tried 10 times
        # tried 3 times
    

    from tenacity import retry, stop_after_delay, TryAgain
    
    
    @retry(stop=stop_after_delay(3))
    def do_something_unreliable(n):
        """显式重试"""
        n += 1
        if n == 10:
            raise TryAgain
        return n
    
    
    n = 0
    while n <= 15:
        try:
            n = do_something_unreliable(n)
        except:
            n += 1
        else:
            print(n, end=' ')
    # 1 2 3 4 5 6 7 8 9 11 12 13 14 15 16
    

    超过最大重试次数或时间后抛出异常

    import time
    from tenacity import retry, stop_after_attempt, stop_after_delay
    
    n = 0
    start = time.time()
    
    
    @retry(reraise=True, stop=stop_after_attempt(3))
    def do_something_unreliable():
        """超过最大重试次数后抛出异常"""
        global n
        n += 1
        raise IOError(f'tried {n} times but failed.')
    
    
    @retry(reraise=True, stop=stop_after_delay(3))
    def do_something_unreliable1():
        """超过最大重试时间后抛出异常"""
        end = time.time()
        raise IOError(f'tried {end - start:.4f} seconds but failed.')
    
    
    try:
        do_something_unreliable()
    except Exception as e:
        print(e)
    try:
        do_something_unreliable1()
    except Exception as e:
        print(e)
    # tried 3 times but failed.
    # tried 2.9864 seconds but failed.
    

    import sys
    import logging
    from tenacity import retry, stop_after_attempt, before_log
    
    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
    
    logger = logging.getLogger(__name__)
    
    
    @retry(stop=stop_after_attempt(3), before=before_log(logger, logging.DEBUG))
    def do_something_unreliable():
        raise IOError('Fail')
    
    
    try:
        do_something_unreliable()
    except Exception as e:
        print(e)
    # RetryError[<Future at 0x1b0342d7a58 state=finished raised OSError>]
    # DEBUG:__main__:Starting call to '__main__.do_something_unreliable', this is the 1st time calling it.
    # DEBUG:__main__:Starting call to '__main__.do_something_unreliable', this is the 2nd time calling it.
    # DEBUG:__main__:Starting call to '__main__.do_something_unreliable', this is the 3rd time calling it.
    

    import sys
    import time
    import logging
    from tenacity import retry, stop_after_attempt, after_log
    
    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
    
    logger = logging.getLogger(__name__)
    
    
    @retry(stop=stop_after_attempt(3), after=after_log(logger, logging.DEBUG))
    def do_something_unreliable():
        time.sleep(0.5)
        raise IOError('Fail')
    
    
    try:
        do_something_unreliable()
    except Exception as e:
        print(e)
    # DEBUG:__main__:Finished call to '__main__.do_something_unreliable' after 0.500(s), this was the 1st time calling it.
    # DEBUG:__main__:Finished call to '__main__.do_something_unreliable' after 1.000(s), this was the 2nd time calling it.
    # DEBUG:__main__:Finished call to '__main__.do_something_unreliable' after 1.500(s), this was the 3rd time calling it.
    # RetryError[<Future at 0x22b45e07a58 state=finished raised OSError>]
    

    import sys
    import time
    import logging
    from tenacity import retry, stop_after_attempt, before_sleep_log
    
    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
    
    logger = logging.getLogger(__name__)
    
    
    @retry(stop=stop_after_attempt(3), before_sleep=before_sleep_log(logger, logging.DEBUG))
    def do_something_unreliable():
        time.sleep(0.5)
        raise IOError('Fail')
    
    
    try:
        do_something_unreliable()
    except Exception as e:
        print(e)
    # DEBUG:__main__:Retrying __main__.do_something_unreliable in 0.0 seconds as it raised OSError: Fail.
    # DEBUG:__main__:Retrying __main__.do_something_unreliable in 0.0 seconds as it raised OSError: Fail.
    # RetryError[<Future at 0x1c840b96ac8 state=finished raised OSError>]
    

    from tenacity import retry, stop_after_attempt
    
    
    @retry(stop=stop_after_attempt(3))
    def do_something_unreliable():
        raise IOError('Fail')
    
    
    try:
        do_something_unreliable()
    except:
        pass
    print(do_something_unreliable.retry.statistics)
    # {'start_time': 756545.281, 'attempt_number': 3, 'idle_for': 0, 'delay_since_first_attempt': 0.0}
    

    do_something_unreliable.retry.statistics["attempt_number"] 可直接获取重试次数,不用手动计算

    retry_state 是 RetryCallState 的实例

    from tenacity import retry, stop_after_attempt, retry_if_result
    
    n = 0
    
    
    def return_value(retry_state):
        """自定义回调函数"""
        return retry_state.outcome.result()  # 返回函数产生的最后结果或异常
    
    
    @retry(stop=stop_after_attempt(3),
           retry_error_callback=return_value,
           retry=retry_if_result(lambda x: isinstance(x, int)))
    def do_something_unreliable():
        global n
        n += 1
        return n
    
    
    print(do_something_unreliable())
    # 3
    

    结合 for 循环和上下文管理器

    from tenacity import Retrying, RetryError, stop_after_attempt
    
    try:
        for attempt in Retrying(stop=stop_after_attempt(3)):
            with attempt:
                raise Exception('Fail')
    except RetryError:
        pass
    

    协程

    from tenacity import AsyncRetrying, RetryError, stop_after_attempt
    
    
    async def function():
        try:
            async for attempt in AsyncRetrying(stop=stop_after_attempt(3)):
                with attempt:
                    raise Exception('Fail')
        except RetryError:
            pass
    

    import trio
    import asks
    import tornado
    from tenacity import retry
    
    
    @retry
    async def my_async_function(loop):
        await loop.getaddrinfo('8.8.8.8', 53)
    
    
    @retry
    @tornado.gen.coroutine
    def my_async_function(http_client, url):
        yield http_client.fetch(url)
    
    
    @retry(sleep=trio.sleep)
    async def my_async_function(loop):
        await asks.get('https://example.org')
    

    参数 功能 取值
    重试直至成功
    stop 停止条件 最大重试次数 stop_after_attempt
    最大重试时间 stop_after_delay
    wait 等待条件 重试间隔 wait_fixed
    随机重试间隔wait_random
    指数级重试间隔 wait_exponential
    重试间隔链 wait_chain
    retry 重试条件 自定义异常 retry_if_exception_type
    自定义返回 retry_if_result
    reraise 是否抛出异常 bool
    before 重试前动作 重试前记日志 before_log
    after 重试后动作 重试后记日志 after_log
    before_sleep 重试失败后动作 before_sleep_log 重试失败后记日志
    retry_error_callback 回调函数

    到此这篇关于Python重试库Retrying和Tenacity的实现的文章就介绍到这了,更多相关Python重试库Retrying和Tenacity内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 

    您可能感兴趣的文章:

    • Python中最强大的重试库Tenacity使用探索
    • Python重试库 Tenacity详解(推荐)
    • python中可以发生异常自动重试库retrying
    • Python中最强大的错误重试库(tenacity库)

    站内搜索