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

相关推荐
asdfg12589631 分钟前
JS中的闭包应用
开发语言·前端·javascript
kirk_wang3 分钟前
Flutter 导航锁踩坑实录:从断言失败到类型转换异常
前端·javascript·flutter
长安牧笛9 分钟前
心理健康情绪日记分析系统,用户输入文字日记后,AI提取情绪关键词,焦虑/愉悦等,生成周情绪波动曲线,并推荐调节建议。
python
艾上编程35 分钟前
第三章——爬虫工具场景之Python爬虫实战:学术文献摘要爬取,助力科研高效进行
开发语言·爬虫·python
Hi_kenyon1 小时前
FastAPI+VUE3创建一个项目的步骤模板(二)
python·fastapi
静小谢1 小时前
前后台一起部署,vite配置笔记base\build
前端·javascript·笔记
拉普拉斯妖1081 小时前
DAY38 Dataset和DataLoader
python
用户47949283569151 小时前
改了CSS刷新没反应-你可能不懂HTTP缓存
前端·javascript·面试
还好还好不是吗2 小时前
老项目改造 vue-cli 2.6 升级 rsbuild 提升开发效率300% upupup!!!
前端·性能优化
sumAll2 小时前
别再手动对齐矩形了!这个开源神器让 AI 帮你画架构图 (Next-AI-Draw-IO 体验)
前端·人工智能·next.js