前言
在Python编程中,装饰器(Decorator)是一个非常重要但又让初学者感到困惑的概念。无论是Flask中的路由装饰器@app.route(),还是Django中的登录验证装饰器@login_required,装饰器无处不在。今天,我们就来彻底搞懂Python装饰器的原理与用法。

什么是装饰器?
一句话定义:装饰器是一个接受函数作为参数、返回新函数的可调用对象(通常也是函数),用于在不修改原函数代码的前提下,为函数添加额外功能。

装饰器遵循开闭原则:对扩展开放,对修改关闭。

为什么需要装饰器?
来看一个实际场景:你有10个函数,现在需要在每个函数执行前打印日志。

方法一(最low):在每个函数内部都加打印语句 → 重复代码,难以维护

方法二(使用装饰器):写一个装饰器,优雅地解决

快速入门:第一个装饰器
python
# 定义一个装饰器函数
def my_decorator(func):
    def wrapper():
        print("函数执行前...")
        func()
        print("函数执行后...")
    return wrapper

# 使用装饰器
@my_decorator
def say_hello():
    print("Hello, World!")

# 调用函数
say_hello()
输出:

text
函数执行前...
Hello, World!
函数执行后...
看到没有?我们没有修改say_hello函数内部,却给它添加了前、后打印的功能。

语法糖背后的原理
@my_decorator 等价于:

python
say_hello = my_decorator(say_hello)
装饰器本质上就是一个函数调用,返回一个新的函数对象。

进阶:装饰带参数的函数
如果原函数有参数,我们的wrapper需要能接收任意参数:

python
def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        print(f"参数: args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"返回值: {result}")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

result = add(3, 5)
print(f"最终结果: {result}")
输出:

text
调用函数: add
参数: args=(3, 5), kwargs={}
返回值: 8
最终结果: 8
高级:带参数的装饰器
有时我们需要让装饰器本身接受参数(比如指定日志级别)。

python
def repeat(times):
    """让函数重复执行times次"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(times):
                print(f"第{i+1}次执行:")
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Hello, {name}!")

greet("CSDN")
输出:

text
第1次执行:
Hello, CSDN!
第2次执行:
Hello, CSDN!
第3次执行:
Hello, CSDN!
注意:三层嵌套函数,最外层接收装饰器参数。

类装饰器
使用类也可以实现装饰器,通过__call__方法:

python
class CountCalls:
    """统计函数被调用的次数"""
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 已被调用 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def test():
    print("执行函数")

test()
test()
test()
输出:

text
test 已被调用 1 次
执行函数
test 已被调用 2 次
执行函数
test 已被调用 3 次
执行函数
保留原函数的元信息
装饰器会覆盖原函数的__name__、__doc__等属性,使用functools.wraps解决:

python
from functools import wraps

def my_decorator(func):
    @wraps(func)  # 这一行是关键
    def wrapper(*args, **kwargs):
        """这是wrapper的文档"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def example():
    """这是example的文档"""
    pass

print(example.__name__)   # 输出: example (而不是wrapper)
print(example.__doc__)    # 输出: 这是example的文档
实战应用场景
1. 计时器装饰器
python
import time

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} 耗时: {end - start:.4f}秒")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "完成"

slow_function()  # 输出: slow_function 耗时: 1.0002秒
2. 权限校验装饰器(模拟)
python
def login_required(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get('is_login', False):
            print("请先登录!")
            return None
        return func(user, *args, **kwargs)
    return wrapper

@login_required
def delete_post(user, post_id):
    print(f"用户{user['name']}删除了文章{post_id}")

# 测试
user1 = {'name': 'Alice', 'is_login': False}
delete_post(user1, 101)  # 输出: 请先登录!

user2 = {'name': 'Bob', 'is_login': True}
delete_post(user2, 102)  # 输出: 用户Bob删除了文章102
3. 缓存装饰器
python
from functools import lru_cache

# Python内置的缓存装饰器
@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(40))  # 计算极快,因为结果被缓存了
多个装饰器的执行顺序
当一个函数有多个装饰器时,执行顺序是从下往上的。

python
def decorator_a(func):
    def wrapper():
        print("A before")
        func()
        print("A after")
    return wrapper

def decorator_b(func):
    def wrapper():
        print("B before")
        func()
        print("B after")
    return wrapper

@decorator_a
@decorator_b
def hello():
    print("Hello")

hello()
输出:

text
A before
B before
Hello
B after
A after
等价于:hello = decorator_a(decorator_b(hello))

常见陷阱与最佳实践
忘记调用wraps:会导致调试困难,函数元信息丢失

装饰器内部函数不返回原函数的返回值:导致原函数的返回值被吞掉

可变对象作为默认参数:如果装饰器中使用了列表等可变对象作为wrapper的默认参数,可能产生意料之外的行为

推荐使用类装饰器来保存状态:当需要记录数据时,类比闭包更清晰

总结
知识点    关键点
基本装饰器    接受函数,返回包装函数
带参数装饰器    三层嵌套,最外层接收参数
类装饰器    实现__init__和__call__
保留元信息    from functools import wraps
执行顺序    从下往上装饰,从上往下执行
装饰器是Python高阶编程的基石之一,掌握它不仅能写出更优雅的代码,还能更深入地理解Flask、Django等框架的设计思想。

思考题:如何实现一个装饰器,使得函数被调用时自动重试3次(如果发生异常)?欢迎在评论区留下你的代码!

更多推荐