You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免嵌套函数使用装饰器时出现重复打印?

解决装饰器嵌套调用重复打印计时的问题

我给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 07:13:33