如何避免嵌套函数使用装饰器时出现重复打印?
解决装饰器嵌套调用重复打印计时的问题
我给func1和func2都加了@time_elapsed计时器装饰器,但func2内部调用func1时,会同时打印func1和func2的计时信息。现在需要实现:单独调用func1时正常打印计时,调用func2时只打印func2的总计时,不输出func1的计时。
原代码
import time from functools import wraps def time_elapsed(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) elapsed_time = time.time() - start_time print(f'{func.__name__} took {elapsed_time:.2f} seconds.') return result return wrapper @time_elapsed def func1(): time.sleep(2) @time_elapsed def func2(): func1() time.sleep(3)
当前行为
func1() # 输出:func1 took 2.01 seconds.
func2() # 输出: # func1 took 2.00 seconds. # func2 took 5.01 seconds.
期望行为
func1() # 输出:func1 took 2.01 seconds.
func2() # 输出:func2 took 5.01 seconds.
三种实用解决方案
方法1:直接调用原函数(最小改动)
functools.wraps会把原函数保留在__wrapped__属性中,在func2里直接调用这个原函数就能绕过装饰器的打印逻辑:
@time_elapsed def func2(): func1.__wrapped__() # 调用未被装饰的原始func1 time.sleep(3)
- 优点:无需修改装饰器或
func1定义,改动极小 - 缺点:依赖
__wrapped__属性(虽为标准行为,但多层装饰器场景需调整)
方法2:给装饰器加开关参数(高灵活性)
修改装饰器为带参数的工厂模式,支持通过调用时的参数控制是否打印计时:
import time from functools import wraps def time_elapsed(print_log=True): def decorator(func): @wraps(func) def wrapper(*args, print_log_override=None, **kwargs): start_time = time.time() result = func(*args, **kwargs) elapsed_time = time.time() - start_time # 优先使用调用时传入的开关,无传入则用装饰器默认值 should_print = print_log_override if print_log_override is not None else print_log if should_print: print(f'{func.__name__} took {elapsed_time:.2f} seconds.') return result return wrapper return decorator # 保持func1的默认打印行为 @time_elapsed() def func1(): time.sleep(2) @time_elapsed() def func2(): func1(print_log_override=False) # 调用时关闭func1的计时打印 time.sleep(3)
- 优点:灵活可控,适合复杂场景下的计时开关需求
- 缺点:需要修改装饰器结构,调用时需额外传参
方法3:拆分核心逻辑(最稳妥)
把func1的核心业务逻辑抽成无装饰器的内部函数,分别定义对外暴露的带装饰器版本和供内部调用的无装饰版本:
import time from functools import wraps def time_elapsed(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) elapsed_time = time.time() - start_time print(f'{func.__name__} took {elapsed_time:.2f} seconds.') return result return wrapper # 核心业务逻辑,不带装饰器 def _func1(): time.sleep(2) # 对外暴露的带计时的func1 @time_elapsed def func1(): return _func1() @time_elapsed def func2(): _func1() # 调用无装饰的核心逻辑,不触发计时打印 time.sleep(3)
- 优点:结构清晰,不依赖特殊属性,无潜在兼容性问题
- 缺点:需要额外定义内部函数,代码量略有增加
内容的提问来源于stack exchange,提问作者VERBOSE
相关产品推荐
相关产品推荐

