Python装饰器解除:如何让被装饰的函数重获自由?

引言:当装饰过头时

装饰器是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):
    # 核心业务逻辑
    ...

当我们需要:

  1. 单元测试只测试核心逻辑
  2. 性能测试排除装饰器开销
  3. 特殊情况下跳过验证

这时就需要解除装饰器!

标准解除方法

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 技术干货!如果觉得有用,记得收藏本文!

相关推荐
深蓝海拓1 分钟前
PySide6从0开始学习的笔记(二十五) Qt窗口对象的生命周期和及时销毁
笔记·python·qt·学习·pyqt
Dfreedom.11 分钟前
开运算与闭运算:图像形态学中的“清道夫”与“修复匠”
图像处理·python·opencv·开运算·闭运算
理人综艺好会11 分钟前
Web学习之用户认证
前端·学习
2301_7903009615 分钟前
用Python读取和处理NASA公开API数据
jvm·数据库·python
葱明撅腚26 分钟前
利用Python挖掘城市数据
python·算法·gis·聚类
We་ct28 分钟前
LeetCode 36. 有效的数独:Set实现哈希表最优解
前端·算法·leetcode·typescript·散列表
Serendipity_Carl33 分钟前
1637加盟网数据实战(数分可视化)
爬虫·python·pycharm·数据可视化·数据清洗
流㶡35 分钟前
网络爬虫之requests.get() 之爬取网页内容
python·数据爬虫
weixin_3954489139 分钟前
main.c_cursor_0129
前端·网络·算法
yuankoudaodaokou1 小时前
高校科研新利器:思看科技三维扫描仪助力精密研究
人工智能·python·科技