Python装饰器原理详解:从入门到实战的高级用法

Python 装饰器原理与高级用法从入门到实战

[导读] 装饰器(Decorator)是Python中最优雅的语法糖之一,也是进阶Python开发的必经之路。本文将深入剖析装饰器执行机制,详解@wraps的作用原理,手把手实现带参数装饰器与类装饰器,并通过5个典型实战案例展示装饰器在权限控制、性能优化等场景的应用。最后提供10道梯度练习题帮助读者全面掌握这一重要特性。


一、装饰器前置知识

1.1 函数是一等公民

Python中函数可作为参数传递、作为返回值、赋值给变量:

def greet(name):
    return f"Hello, {name}!"

# 函数赋值
say_hi = greet
print(say_hi("Alice"))  # Hello, Alice!

# 函数作为参数
def call_func(func, arg):
    return func(arg)
    
print(call_func(greet, "Bob"))  # Hello, Bob!

1.2 闭包(Closure)

内部函数捕获外部作用域变量的机制:

def outer(msg):
    def inner():
        print(f"Message: {msg}")  # 捕获外部变量msg
    return inner

closure = outer("Closure works!")
closure()  # Message: Closure works!

关键点:闭包会持有外部变量的引用,可能导致变量值延迟绑定问题


二、装饰器核心原理

2.1 基础装饰器实现

def simple_decorator(func):
    def wrapper():
        print("Before function call")
        func()
        print("After function call")
    return wrapper

@simple_decorator
def say_hello():
    print("Hello!")

say_hello()

执行流程

  1. @simple_decorator等价于 say_hello = simple_decorator(say_hello)
  2. 调用say_hello()实际执行wrapper()

2.2 使用@wraps保持元信息

from functools import wraps

def keep_metadata(func):
    @wraps(func)  # 保留原始函数信息
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@keep_metadata
def sample():
    """This is a docstring"""
    pass

print(sample.__name__)  # 输出'sample'(不使用@wraps会显示'wrapper')
print(sample.__doc__)   # 显示原始文档字符串

三、进阶装饰器实现

3.1 带参数的装饰器

def repeat(num_times):
    # 外层处理装饰器参数
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def countdown(n):
    while n > 0:
        print(n)
        n -= 1

countdown(2)  # 打印2 1 各三次

3.2 类装饰器

class TraceCall:
    def __init__(self, func):
        self.func = func
        self.call_count = 0

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"Call {self.call_count} to {self.func.__name__}")
        return self.func(*args, **kwargs)

@TraceCall
def compute(x, y):
    return x * y

compute(3, 4)  # 输出调用信息并返回12

四、实战场景案例

4.1 接口权限验证

def require_role(role):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.role != role:
                raise PermissionError(f"Requires {role} role")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

class User:
    def __init__(self, role):
        self.role = role

@require_role("admin")
def delete_database(user):
    print("Database deleted!")

admin = User("admin")
user = User("guest")
delete_database(admin)  # 正常执行
delete_database(user)   # 抛出PermissionError

4.2 函数执行时间统计

import time
from functools import wraps

def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} took {elapsed:.4f} seconds")
        return result
    return wrapper

@timeit
def heavy_computation(n):
    return sum(i*i for i in range(n))

heavy_computation(10**6)  # 输出执行时间

五、装饰器堆叠与执行顺序

多个装饰器按从下往上的顺序应用:

@decorator1
@decorator2
def my_func():
    pass
# 等价于 my_func = decorator1(decorator2(my_func))

执行顺序演示

def add_tag(tag):
    def decorator(func):
        def wrapper():
            return f"<{tag}>{func()}</{tag}>"
        return wrapper
    return decorator

@add_tag("div")
@add_tag("strong")
def get_text():
    return "Hello World"

print(get_text())  
# 输出:<div><strong>Hello World</strong></div>

六、练习题

  1. 实现LRU缓存装饰器,限制最大缓存条目

  2. 编写重试装饰器,支持设置重试次数和延迟时间

  3. 创建类型检查装饰器,验证函数参数类型

  4. 实现单例模式类装饰器

  5. 开发路由注册装饰器(模拟Flask路由系统)

  6. 设计并发限制装饰器(控制同时执行线程数)

  7. 编写日志记录装饰器,记录函数调用参数和返回值

  8. 实现自动注册所有派生类的基类装饰器

  9. 创建异步函数超时控制装饰器

  10. 开发装饰器调试工具,打印完整调用堆栈

    源码在博文顶部点击下载


最佳实践建议

  1. 优先使用functools.wraps保持函数元信息
  2. 避免在装饰器中修改原始函数签名
  3. 谨慎处理装饰器中的状态保持
  4. 使用类装饰器处理复杂状态管理
  5. 通过单元测试验证装饰器行为

通过系统学习+实践练习,开发者可充分运用装饰器实现关注点分离、代码复用等目标,写出更优雅、可维护的Python代码。

作者:wolf犭良

物联沃分享整理
物联沃-IOTWORD物联网 » Python装饰器原理详解:从入门到实战的高级用法

发表回复