
python装饰器提供了一种优雅的方式来在不修改原函数代码的情况下,为其添加额外的功能,例如日志记录、性能计时、权限检查等。然而,当一个被装饰的函数在其内部又调用了另一个同样被装饰的函数时,就会出现一个常见的挑战:装饰器的功能可能会被重复执行,导致不必要的输出或行为。
考虑一个简单的计时装饰器 @time_elapsed,它测量函数的执行时间并打印出来。如果我们将它应用于 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(0.1)
@time_elapsed
def func2():
func1() # 调用了func1
time.sleep(0.2)
# 期望行为:
# func1() -> func1 took 0.10 seconds.
# func2() -> func2 took 0.30 seconds. (只打印func2的总时间)
# 实际行为:
# func2()
# func1 took 0.10 seconds. # 冗余输出
# func2 took 0.30 seconds.上述代码在调用 func2() 时,会先打印 func1 的计时,再打印 func2 的计时。这通常不是我们期望的行为,因为我们可能只关心最外层函数的总执行时间。
为了解决这个问题,我们可以修改装饰器,使其能够感知当前的调用深度,并根据预设的深度阈值来决定是否执行其核心逻辑。这可以通过在装饰器函数本身上维护一个内部计数器来实现。
以下是修改后的 time_elapsed 装饰器实现:
立即学习“Python免费学习笔记(深入)”;
import time
from functools import wraps
def time_elapsed(func):
# 定义打印计时信息的最大嵌套深度。
# DEPTH = 1 意味着只打印最外层函数的计时。
# DEPTH = 2 意味着打印最外层函数及其直接子函数的计时。
DEPTH = 1
# 初始化一个内部计数器,用于追踪当前装饰器调用栈的深度。
# 首次调用时,time_elapsed._timer_running 为 0。
if not hasattr(time_elapsed, '_timer_running'):
time_elapsed._timer_running = 0
@wraps(func)
def wrapper(*args, **kwargs):
# 如果当前调用深度已达到或超过预设的阈值,
# 则直接执行原函数,不进行计时和打印。
if time_elapsed._timer_running >= DEPTH:
return func(*args, **kwargs)
# 否则,当前调用在允许的深度范围内,增加计数器。
time_elapsed._timer_running += 1
# 执行计时逻辑
start_time = time.time()
result = func(*args, **kwargs)
elapsed_time = time.time() - start_time
print(f'{func.__name__} took {elapsed_time:.2f} seconds.')
# 计时完成后,减少计数器,恢复到上一层深度。
time_elapsed._timer_running -= 1
return result
return wrapper
# 应用到多个函数,包括嵌套调用
@time_elapsed
def func1():
time.sleep(0.1)
@time_elapsed
def func2():
func1()
time.sleep(0.2)
@time_elapsed
def func3():
func1()
func2()
time.sleep(0.3)
@time_elapsed
def func4():
func1()
func2()
func3()
time.sleep(0.4)
if __name__ == "__main__":
print("--- Testing func1 ---")
func1()
print("\n--- Testing func2 ---")
func2()
print("\n--- Testing func3 ---")
func3()
print("\n--- Testing func4 ---")
func4()当 DEPTH = 1 时,运行上述代码将得到以下输出:
--- Testing func1 --- func1 took 0.10 seconds. --- Testing func2 --- func2 took 0.30 seconds. --- Testing func3 --- func3 took 0.70 seconds. --- Testing func4 --- func4 took 1.50 seconds.
可以看到,只有最外层的函数调用被计时并打印。内部 func1、func2、func3 的调用虽然仍然通过了装饰器,但由于 _timer_running 计数器已经达到或超过 DEPTH,它们的计时和打印逻辑被跳过。
这个解决方案的强大之处在于 DEPTH 参数的可配置性。如果你希望看到更深层次的调用计时,只需修改 DEPTH 的值。
例如,将 DEPTH = 2:
# ... (其他代码相同)
def time_elapsed(func):
DEPTH = 2 # 允许打印两层嵌套的计时信息
# ... (其他代码相同)再次运行 if __name__ == "__main__": 块,输出将变为:
--- Testing func1 --- func1 took 0.10 seconds. --- Testing func2 --- func1 took 0.10 seconds. # func1 作为 func2 的直接子函数,被打印 func2 took 0.30 seconds. --- Testing func3 --- func1 took 0.10 seconds. # func1 作为 func3 的直接子函数,被打印 func2 took 0.30 seconds. # func2 作为 func3 的直接子函数,被打印 func3 took 0.70 seconds. --- Testing func4 --- func1 took 0.10 seconds. # func1 作为 func4 的直接子函数,被打印 func2 took 0.30 seconds. # func2 作为 func4 的直接子函数,被打印 func3 took 0.70 seconds. # func3 作为 func4 的直接子函数,被打印 func4 took 1.50 seconds.
现在,func2 内部调用的 func1 的计时被打印了出来,因为它的调用深度是 2(相对于 func2 是 1,相对于最初的外部调用是 2),这仍然在 DEPTH = 2 的允许范围内。但 func3 内部调用的 func1 和 func2 仍然只打印了一次,因为它们是 func3 的直接子函数,深度为 2。而 func4 内部调用的 func1、func2 和 func3 也都打印了,因为它们相对于 func4 都是第一层嵌套,总深度为 2。
线程安全: 上述 _timer_running 计数器是直接附加在 time_elapsed 函数对象上的,这意味着它是一个全局状态。在多线程环境中,多个线程同时调用被装饰函数时,这个计数器可能会出现竞态条件,导致不正确的行为。在多线程应用中,应考虑使用 threading.local() 来为每个线程维护独立的计数器。
import threading
def time_elapsed_thread_safe(func):
_local = threading.local()
_local.timer_running = 0 # 每个线程有自己的计数器
DEPTH = 1
@wraps(func)
def wrapper(*args, **kwargs):
# ... 使用 _local.timer_running 代替 time_elapsed._timer_running ...
if _local.timer_running >= DEPTH:
return func(*args, **kwargs)
_local.timer_running += 1
# ... 计时逻辑 ...
_local.timer_running -= 1
return result
return wrapper通用性: 这种基于计数器的深度控制方法不仅适用于计时装饰器,也适用于任何需要在嵌套调用中控制行为的装饰器,如日志记录、缓存等。
代码清晰度: 这种方法在不修改原有函数调用结构的前提下,通过装饰器内部的逻辑巧妙地解决了问题,保持了代码的清晰度和模块化。
通过这种智能的深度控制机制,我们可以精确地管理装饰器在复杂函数调用链中的行为,避免冗余输出,并根据实际需求调整信息的粒度,从而提高代码的可维护性和用户体验。
以上就是Python装饰器在嵌套函数中避免重复输出的策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号