Python 3.7+中如何可靠判断方法是否被装饰器包裹?
可靠判断方法是否被装饰的实现方案
问题背景
在Python 3.7+环境下,现有装饰器与类定义如下(修正原代码中@dec()的语法错误,装饰器无需加括号):
import functools def dec(method): @functools.wraps(method) def wrapper(*args, **kwargs): print("wrapped") return method(*args, **kwargs) return wrapper class A: @dec def f1(self): print(is_wrapped()) def f2(self): print(is_wrapped())
需求是:
- 调用
A().f1()时输出True - 调用
A().f2()时输出False
但原有is_wrapped实现存在漏洞:若f2的调用者局部变量中包含未被装饰的f2引用,会导致判断结果错误。
已知限制条件:
is_wrapped直接被目标方法调用,inspect.currentframe().f_back指向该方法的栈帧is_wrapped无法接收任何参数- 装饰器必须使用
functools.wraps处理被包裹函数
可行解决方案
核心思路是给被装饰后的方法添加唯一标识,is_wrapped通过检查该标识来准确判断方法是否被装饰,不受调用者局部变量干扰。
方案1:自定义装饰标记(推荐)
1. 修改装饰器,添加自定义属性
在装饰器中给生成的wrapper方法添加一个自定义标记属性:
import functools def dec(method): @functools.wraps(method) def wrapper(*args, **kwargs): print("wrapped") return method(*args, **kwargs) # 添加自定义标记,标识该方法是被dec装饰后的实例 wrapper._is_decorated_by_dec = True return wrapper
2. 实现is_wrapped函数
通过栈帧获取当前调用的方法对象,检查是否存在自定义标记:
import inspect def is_wrapped(): caller_frame = inspect.currentframe().f_back try: # 从栈帧中获取调用方法的实例和方法名 instance = caller_frame.f_self method_name = caller_frame.f_code.co_name # 获取实例上绑定的实际方法对象 current_method = getattr(instance, method_name) # 检查是否存在装饰器添加的标记 return hasattr(current_method, '_is_decorated_by_dec') finally: # 手动删除栈帧引用,避免内存泄漏 del caller_frame
方案2:利用functools.wraps的__wrapped__属性
无需修改装饰器,直接检查方法是否存在__wrapped__属性(functools.wraps会自动给wrapper添加该属性,指向原始方法):
import inspect def is_wrapped(): caller_frame = inspect.currentframe().f_back try: instance = caller_frame.f_self method_name = caller_frame.f_code.co_name current_method = getattr(instance, method_name) # 检查方法是否被functools.wraps包裹(即是否是装饰后的方法) return hasattr(current_method, '__wrapped__') finally: del caller_frame
测试验证
执行以下代码:
a = A() a.f1() # 先输出"wrapped",再输出True a.f2() # 输出False
两种方案均能满足需求,且不受调用者局部变量的干扰。方案1的自定义标记更明确,可避免与其他装饰器的__wrapped__属性产生混淆;方案2无需修改原有装饰器,更简洁。
内容的提问来源于stack exchange,提问作者Tomer Wolberg
相关产品推荐
相关产品推荐

