装饰器#

装饰器(Decorator)是 Python 中一个强大且优雅的特性,它允许你以非侵入式的方式为函数或方法添加功能,而无需修改原函数的代码。装饰器广泛用于日志记录、性能测试、权限验证等场景。

函数是一等公民#

在 Python 中,函数是一等公民(First-Class Object),意味着函数可以像变量一样被赋值、传递、返回。这为装饰器奠定了基础。

装饰器本质上是一个函数,它接受另一个函数作为输入,并返回一个新的函数(包装函数)。

# 函数赋值给变量
def greet(name):
    return f"Hello, {name}!"

hello = greet  # 函数赋值
print(hello("World"))  # 输出: Hello, World!

# 函数作为参数传递
def execute(func, arg):
    return func(arg)

print(execute(greet, "Python"))  # 输出: Hello, Python!
Hello, World!
Hello, Python!

简单装饰器:无参数装饰器#

最基本的装饰器是一个函数,它包裹原函数,并在调用前后添加逻辑。

示例:添加打印日志的装饰器#

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"函数 {func.__name__} 开始执行...")
        result = func(*args, **kwargs)  # 调用原函数
        print(f"函数 {func.__name__} 执行完成!")
        return result
    return wrapper

# 使用装饰器
@my_decorator  # 等价于: greet = my_decorator(greet)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")  # 输出:
# 函数 greet 开始执行...
# Hello, Alice!
# 函数 greet 执行完成!
函数 greet 开始执行...
Hello, Alice!
函数 greet 执行完成!

上面代码说明:

  • my_decorator 是装饰器函数。

  • wrapper 是内层包装函数,接收原函数的所有参数(*args, **kwargs)。

  • @my_decorator 语法糖:自动将 greet 替换为 my_decorator(greet)

带参数的装饰器#

有时装饰器需要接受参数(如配置选项)。这时,需要一个三层嵌套结构:外层函数接收装饰器参数,返回一个装饰器函数。

示例:可配置的重复执行装饰器#

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

# 使用:重复执行 3 次
@repeat(times=3)
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("Bob")  # 输出: Hello, Bob! (三次)
Hello, Bob!
Hello, Bob!
Hello, Bob!

上面代码说明:

  • repeat(times):外层接收参数 times,返回装饰器 decorator

  • decorator(func):接收原函数,返回 wrapper

类装饰器#

装饰器也可以是类,通过 __call__ 方法实现。

示例:计数器类装饰器#

class Counter:
    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)

@Counter
def multiply(x, y):
    return x * y

print(multiply(2, 3))  # 输出: 函数 multiply 已调用 1 次 \n 6
print(multiply(4, 5))  # 输出: 函数 multiply 已调用 2 次 \n 20
函数 multiply 已调用 1 次
6
函数 multiply 已调用 2 次
20

上面代码说明:

  • 类实例化时接收原函数(__init__)。

  • 每次调用实例时,触发 __call__,执行原函数并添加逻辑。

  • 适合需要状态(如计数)的场景。

高级用法:使用 functools.wraps#

直接使用 wrapper 会丢失原函数的元信息(如 __name__docstring)。我们可以使用 functools.wraps 来修复。记住:始终使用 @wraps(func) 保持函数签名完整

from functools import wraps

def my_decorator(func):
    @wraps(func)  # 复制原函数的元信息
    def wrapper(*args, **kwargs):
        print("执行前...")
        result = func(*args, **kwargs)
        print("执行后...")
        return result
    return wrapper

@my_decorator
def greet(name):
    """问候函数"""
    return f"Hello, {name}!"

print(greet.__name__)  # 输出: greet (而非 wrapper)
print(greet.__doc__)   # 输出: 问候函数
greet
问候函数

实用示例:性能计时装饰器#

一个常见应用:测量函数执行时间。

import time
from functools import wraps

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

@timer
def slow_function(n):
    time.sleep(1)  # 模拟耗时操作
    return sum(range(n))

print(slow_function(1000))  # 输出执行时间约 1 秒
slow_function 执行时间: 1.0051 秒
499500

多个装饰器和嵌套#

装饰器可以堆叠,按从下到上的顺序执行(自内而外)。

@timer
@my_decorator
def combined_func(name):
    print(f"核心逻辑: {name}")

combined_func("Test")
# 先执行 my_decorator 的逻辑,然后 timer 的
执行前...
核心逻辑: Test
执行后...
combined_func 执行时间: 0.0001 秒

多个装饰器叠加顺序#

@d2
@d1
def func(): ...

等价于 func = d2(d1(func))。执行顺序:d1 先包,但 d2 的外层 wrapper 先运行。