🧠 装饰器的核心概念

1. 函数是一等公民

在Python中,函数和其他数据类型(如整数、字符串)一样,可以:

  • 赋值给变量
  • 作为参数传递给其他函数
  • 作为其他函数的返回值
# 定义一个普通函数
def greet(name):
    return f”你好, {name}!”

# 将函数赋值给变量
say_hello = greet
print(say_hello(“小明”)) # 输出: 你好, 小明!

2. 装饰器的工作原理

装饰器本质上是一个接受函数作为参数返回一个新函数的函数。

装饰器执行流程

1. 调用装饰器函数,并传入原始函数
2. 装饰器内部定义一个新函数(包装器)
3. 在包装器内部添加新功能并调用原始函数
4. 返回新函数替代原始函数

🔧 创建和使用装饰器

1. 基础装饰器

# 定义装饰器
def my_decorator(func):
    def wrapper():
        print(“函数执行前做一些事情”)
        func() # 调用原始函数
        print(“函数执行后做一些事情”)
    return wrapper

# 使用装饰器
@my_decorator
def say_hello():
    print(“大家好!”)

# 调用被装饰的函数
say_hello()

2. 处理带有参数的函数

使用 *args 和 **kwargs 接收任意数量和类型的参数

def smart_decorator(func):
    def wrapper(*args, **kwargs):
        print(f”准备调用函数: {func.__name__}”)
        result = func(*args, **kwargs)
        print(f”函数 {func.__name__} 执行完毕”)
        return result
    return wrapper

@smart_decorator
def greet(name, greeting=”你好”):
    return f”{greeting}, {name}!”

print(greet(“小明”, greeting=”早安”))

3. 带参数的装饰器

有时需要给装饰器本身传递参数(例如设置日志级别)

# 三层嵌套函数实现带参数装饰器
def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def say_hello(name):
    print(f”你好, {name}!”)

say_hello(“小明”)

4. 类装饰器

除了函数装饰器,你还可以创建类装饰器:

class TimerDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        import time
        start_time = time.time()
        result = self.func(*args, **kwargs)
        end_time = time.time()
        print(f”{self.func.__name__} 执行耗时: {end_time – start_time:.4f}秒”)
        return result

@TimerDecorator
def long_running_function():
    total = 0
    for i in range(10000000):
        total += i
    return total

long_running_function()

💡 装饰器的常见应用场景

1. 日志记录

记录函数的执行时间、参数等信息

2. 性能测试

测量函数执行时间,用于性能优化

3. 权限验证

检查用户权限后再执行函数

4. 数据缓存

存储函数结果,避免重复计算

5. 错误处理

统一捕获和处理异常

6. 路由注册

Web框架(如Flask)中使用装饰器定义路由

⚠️ 注意事项

1. 使用 functools.wraps 保留原始函数的元信息
2. 多个装饰器的执行顺序是从下往上
3. 理解装饰器在代码导入时立即执行