引言:当装饰过头时
装饰器是Python中强大的工具,它可以优雅地增强函数功能。但有时我们需要"解除装饰":
- 调试时查看原始函数行为
- 性能测试需要绕过装饰器的额外开销
- 在单元测试中单独测试原始函数
- 动态启用/禁用装饰功能
本文将带你探索Python中装饰器解除的奥秘。
理解装饰的本质
1.1 装饰器的底层原理
装饰器本质上是一个语法糖:
python
@decorator
def my_func(): ...
等价于:
python
def my_func(): ...
my_func = decorator(my_func)
装饰后,my_func
实际上指向了装饰器返回的新函数。
1.2 为什么需要解除装饰?
考虑这个场景:
python
@log_calls
@validate_input
def process_data(data):
# 核心业务逻辑
...
当我们需要:
- 单元测试只测试核心逻辑
- 性能测试排除装饰器开销
- 特殊情况下跳过验证
这时就需要解除装饰器!
标准解除方法
2.1 使用__wrapped__
属性
Python标准库的functools.wraps
提供了装饰器解除的关键:
python
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def original_function():
print("原始函数")
# 调用装饰器后的函数
original_function()
# 解除装饰器
raw_function = original_function.__wrapped__
raw_function() # 直接调用原始函数,不打印日志
代码解释 : - @wraps(func)
:复制原始函数的名称、文档等元信息,设置__wrapped__
属性指向原始函数,保留函数的签名信息
多层装饰器的解除
3.1 处理多个装饰器的情况
python
@decorator1
@decorator2
@decorator3
def my_func(): ...
解除方法:
python
# 只解除最上面
raw1 = my_func.__wrapped__
# 解除所有层
def full_unwrap(func):
while hasattr(func, '__wrapped__'):
func = func.__wrapped__
return func
original_func = full_unwrap(my_func)
3.2 多层解除的实际应用
python
from functools import wraps
import time
def timing(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"耗时: {time.time()-start:.4f}s")
return result
return wrapper
def log_args(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"参数: {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
@timing
@log_args
def compute(x, y):
return x * y
# 解除所有层
def full_unwrap(func):
while hasattr(func, '__wrapped__'):
func = func.__wrapped__
return func
print('-----装饰器-----')
print(compute(1, 2))
# 性能测试时只解除日志装饰
perf_test_func = compute.__wrapped__
print('-----解除耗时装饰器-----')
print(perf_test_func(1,2))
# 完全原始函数
print('-----完全解除装饰器-----')
raw_compute = full_unwrap(compute)
print(raw_compute(1, 2))
自定义装饰器解除方案
4.1 当__wrapped__
不可用时
有些装饰器未使用functools.wraps
,我们需要手动处理:
python
def custom_decorator(func):
def wrapper(*args, **kwargs):
print("装饰逻辑")
return func(*args, **kwargs)
# 手动保存原始函数引用
wrapper.original_func = func
return wrapper
@custom_decorator
def my_func():
print("原始函数")
# 解除装饰
raw_func = my_func.original_func
4.2 使用闭包访问原始函数
python
def another_decorator(func):
def wrapper():
print("装饰前")
func()
print("装饰后")
return wrapper
@another_decorator
def target_func():
print("目标函数")
# 通过闭包访问原始函数
if target_func.__closure__:
for cell in target_func.__closure__:
if callable(cell.cell_contents):
raw_func = cell.cell_contents
break
raw_func() # 直接调用原始函数
结语
点个赞,关注我获取更多实用 Python 技术干货!如果觉得有用,记得收藏本文!