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

相关推荐
yume_sibai1 小时前
06-Rust Web 开发实战(Axum 框架 + 数据库 + JWT 认证 + 中间件 + 部署)
前端·数据库·rust
威联通安全存储1 小时前
TS-h2287XU-RP 在家电制造总装与质检数据场景的部署
python·制造
泡泡鱼(敲代码中)2 小时前
Python 字符串 str 完整学习笔记
python·学习
计算机魔术师2 小时前
特朗普上台打给黄仁勋:AI末日论是骗局,我们绝不让它发生
前端
troy1282 小时前
Python 基础语法(八):Web 后端开发、数据分析与可视化、网络爬虫、人工智能 / 大模型应用
前端·python·数据分析
计算机魔术师3 小时前
CEO说要慢下来,黑客说别做梦了——同一篇论文,两种命运
前端
我不会起名字3223 小时前
一天一道算法题(34):回溯法的经典例题(子集)
java·数据结构·python·算法·golang·深度优先·力扣
kyriewen3 小时前
我花3天抓了一个幽灵bug,凶手藏在第4层
前端·javascript·程序员
ThornArmor3 小时前
重铸1996|肉体渲染:关节的裂缝:分段模型积木拼装与未成熟的 RSP 矩阵骨骼动画形变
c语言·汇编·c++·python·硬件架构·游戏机
毕业设计7034 小时前
(免费领源码)基于Python的博物馆研学活动管理系统的设计与实现22724- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
python·mysql·随机森林·pycharm·django·flask·推荐算法