Python如何叠加使用多个装饰器?多装饰器执行行为解析
Python多装饰器叠加执行规则解答
双装饰器直接组合的有效性
你写的backoff.on_exception+@timeit的组合写法语法完全合法,可以正常运行,但必须先明确Python装饰器的核心执行规则:装饰器是从距离被装饰函数最近的位置开始,由下往上逐层包装的,你写的这段代码:
@backoff.on_exception(backoff.expo, (requests.exceptions.Timeout, requests.exceptions.ConnectionError)) @timeit def get_url(url): return requests.get(url)
等价于手动执行包装逻辑:
def get_url(url): return requests.get(url) get_url = timeit(get_url) get_url = backoff.on_exception(backoff.expo, (requests.exceptions.Timeout, requests.exceptions.ConnectionError))(get_url)
@timeit装饰器的统计范围
这也是核心疑问:按上面的写法,@timeit紧贴原始请求函数,它只包装最原生的requests.get单次调用逻辑:
- 每次触发重试时,backoff逻辑会重新调用被timeit包装的单次请求函数,因此每一次单独的请求耗时都会被timeit单独统计,重试之间的指数退避等待时间不会被计入统计。
- 举个实际场景:第一次请求超时耗时1s,退避等待2s后第二次请求耗时0.5s成功,timeit会先后输出两条统计结果,分别对应1s、0.5s的两次请求耗时,不会统计包含等待时间在内的3.5s总时长。
- 如果你需要统计包含所有重试、退避等待的完整流程总耗时,只需要把
@timeit放到所有backoff装饰器的最外层(最顶部)即可,此时timeit包装的是整个带重试逻辑的完整流程,只会在全流程结束后输出一次总耗时。
多层backoff装饰器叠加的运行行为
你写的三层装饰器代码:
@backoff.on_exception(backoff.expo, SomeOtherError) @backoff.on_exception(backoff.expo, (requests.exceptions.Timeout, requests.exceptions.ConnectionError)) @timeit def get_url(url): return requests.get(url)
执行逻辑是分层嵌套的,从内到外的行为如下:
- 最内层是网络错误专属的退避重试逻辑:只要单次请求抛出Timeout/ConnectionError,这一层就会独立触发指数退避重试,直到达到该层配置的最大重试次数后,才会把未处理的异常向外层抛出。
- 外层是SomeOtherError专属的退避重试逻辑:它只会接收到内层没处理完抛上来的异常,只有当异常类型匹配SomeOtherError时,才会触发自己的指数退避重试,重新调用内层的整套逻辑(也就是重新走内层的网络错误重试流程)。
- 只要执行到最原始的请求调用,@timeit就会统计这一次单次请求的耗时,退避等待时间始终不会被计入timeit的统计结果。
内容的提问来源于stack exchange,提问作者Exploring
相关产品推荐
相关产品推荐

