Python 装饰器(Decorators)是 Python 语言中一个强大且优雅的特性,它允许我们在不修改原有函数代码的情况下,动态地改变函数的行为。装饰器广泛应用于日志记录、权限验证、性能监控、缓存等场景。本文将从基础概念入手,逐步深入到高级应用,帮助你全面掌握 Python 装饰器的使用。

什么是装饰器?

装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。这个新函数通常会在调用原始函数前后执行一些额外的逻辑。装饰器的核心思想是“包装”——将原函数包裹在另一个函数中,从而在不改变原函数代码的前提下扩展其功能。

装饰器的语法

在 Python 中,装饰器使用 @ 符号来应用。例如:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

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

say_hello()

输出:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

这里,@my_decorator 就是装饰器的语法糖,它等价于 say_hello = my_decorator(say_hello)

装饰器的工作原理

要理解装饰器,首先需要理解 Python 中的函数是一等公民(first-class objects)。这意味着函数可以作为参数传递给其他函数,也可以作为返回值从函数中返回。

高阶函数

高阶函数是指接受一个或多个函数作为参数,或者返回一个函数的函数。装饰器就是一个典型的高阶函数。

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

def loud_greet(func):
    def wrapper(name):
        return func(name).upper() + "!!!"
    return wrapper

greet = loud_greet(greet)
print(greet("Alice"))  # 输出: HELLO, ALICE!!!!

闭包

装饰器通常利用闭包(closure)来保存状态。闭包是指一个函数引用了它外部作用域的变量,即使外部函数已经执行完毕。

def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

c = counter()
print(c())  # 1
print(c())  # 2
print(c())  # 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 greet(name):
    print(f"Hello, {name}!")

greet("Bob")

输出:

Hello, Bob!
Hello, Bob!
Hello, Bob!

类装饰器

除了函数装饰器,Python 还支持类装饰器。类装饰器是通过实现 __call__ 方法来实现的。

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

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

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

say_hello()
say_hello()

输出:

Call 1 of say_hello
Hello!
Call 2 of say_hello
Hello!

装饰器的常见应用场景

1. 日志记录

装饰器可以方便地记录函数的调用信息。

def log_function_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

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

add(3, 5)

输出:

Calling add with args=(3, 5), kwargs={}
add returned 8

2. 权限验证

在 Web 开发中,装饰器常用于检查用户权限。

def requires_admin(func):
    def wrapper(user, *args, **kwargs):
        if user.get("role") != "admin":
            raise PermissionError("Admin access required")
        return func(user, *args, **kwargs)
    return wrapper

@requires_admin
def delete_user(user, username):
    print(f"User {username} deleted by {user['name']}")

admin_user = {"name": "Alice", "role": "admin"}
regular_user = {"name": "Bob", "role": "user"}

delete_user(admin_user, "charlie")  # Works
delete_user(regular_user, "dave")   # Raises PermissionError

3. 性能监控

装饰器可以用来测量函数的执行时间。

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)

slow_function()

输出:

slow_function took 1.0012 seconds

4. 缓存

装饰器可以用于缓存函数的结果,避免重复计算。

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(30))  # 计算速度快

装饰器的高级技巧

1. 使用 functools.wraps

当使用装饰器时,原函数的元信息(如函数名、文档字符串)会被替换为包装函数的信息。使用 functools.wraps 可以保留原函数的元信息。

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper function"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def example():
    """Example function"""
    pass

print(example.__name__)  # 输出: example
print(example.__doc__)   # 输出: Example function

2. 多个装饰器的组合

多个装饰器可以堆叠使用,它们的执行顺序是从下往上(靠近函数的装饰器先执行)。

def decorator1(func):
    def wrapper():
        print("Decorator 1 before")
        func()
        print("Decorator 1 after")
    return wrapper

def decorator2(func):
    def wrapper():
        print("Decorator 2 before")
        func()
        print("Decorator 2 after")
    return wrapper

@decorator1
@decorator2
def my_function():
    print("Function executed")

my_function()

输出:

Decorator 1 before
Decorator 2 before
Function executed
Decorator 2 after
Decorator 1 after

3. 类方法装饰器

装饰器也可以用于类的方法,但需要注意 self 参数。

def method_decorator(func):
    def wrapper(self, *args, **kwargs):
        print(f"Calling {func.__name__} on {self}")
        return func(self, *args, **kwargs)
    return wrapper

class MyClass:
    @method_decorator
    def my_method(self, x):
        return x * 2

obj = MyClass()
print(obj.my_method(5))  # 输出: 10

4. 装饰器带参数和实例

有时候,我们需要装饰器既能接受参数,又能作用于类实例。

class DecoratorWithArgs:
    def __init__(self, arg):
        self.arg = arg

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(f"Decorator arg: {self.arg}")
            return func(*args, **kwargs)
        return wrapper

@DecoratorWithArgs("test")
def my_func():
    print("Inside my_func")

my_func()

输出:

Decorator arg: test
Inside my_func

装饰器的注意事项

1. 调试困难

装饰器会改变函数的调用栈,使得调试变得困难。使用 functools.wraps 可以部分缓解这个问题。

2. 性能开销

装饰器会增加函数调用的开销,特别是在多层装饰器的情况下。在性能敏感的场景下需要谨慎使用。

3. 保持接口一致

确保装饰器返回的函数与原函数具有相同的签名,或者使用 *args**kwargs 来保持通用性。

实际案例:构建一个完整的缓存装饰器

让我们构建一个功能完整的缓存装饰器,支持过期时间、最大缓存大小等参数。

import time
from collections import OrderedDict
from functools import wraps

def cache_with_expiry(expiry_seconds=60, max_size=100):
    """
    带过期时间和大小限制的缓存装饰器
    """
    def decorator(func):
        # 使用 OrderedDict 来维护缓存顺序
        cache = OrderedDict()
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 创建缓存键(简化版本,实际应用中需要处理更复杂的情况)
            key = str(args) + str(kwargs)
            
            # 检查缓存是否存在且未过期
            if key in cache:
                result, timestamp = cache[key]
                if time.time() - timestamp < expiry_seconds:
                    # 移动到末尾,表示最近使用
                    cache.move_to_end(key)
                    return result
                else:
                    # 过期,删除
                    del cache[key]
            
            # 调用原函数
            result = func(*args, **kwargs)
            
            # 检查缓存大小
            if len(cache) >= max_size:
                # 删除最旧的缓存项
                cache.popitem(last=False)
            
            # 添加到缓存
            cache[key] = (result, time.time())
            
            return result
        
        # 添加缓存清除方法
        def clear_cache():
            cache.clear()
        
        wrapper.clear_cache = clear_cache
        
        return wrapper
    
    return decorator

# 使用示例
@cache_with_expiry(expiry_seconds=10, max_size=3)
def expensive_operation(x, y):
    print(f"Computing {x} + {y}")
    time.sleep(0.1)  # 模拟耗时操作
    return x + y

# 第一次调用,会执行计算
print(expensive_operation(1, 2))  # Computing 1 + 2 \n 3

# 第二次调用相同参数,会使用缓存
print(expensive_operation(1, 2))  # 3 (无打印)

# 不同参数,会执行计算
print(expensive_operation(2, 3))  # Computing 2 + 3 \n 5

# 清除缓存
expensive_operation.clear_cache()
print(expensive_operation(1, 2))  # Computing 1 + 2 \n 3

总结

Python 装饰器是一个强大而灵活的工具,它允许我们以声明式的方式为函数添加额外功能。通过本文的介绍,你应该已经掌握了:

  1. 基础概念:装饰器的定义、语法和工作原理
  2. 进阶用法:带参数的装饰器、类装饰器、装饰器组合
  3. 实际应用:日志记录、权限验证、性能监控、缓存等场景
  4. 高级技巧:使用 functools.wraps、处理类方法、构建复杂装饰器
  5. 注意事项:调试、性能、接口一致性等问题

装饰器是 Pythonic 编程风格的体现,合理使用装饰器可以让你的代码更加简洁、可读和可维护。记住,装饰器的核心思想是“包装”和“扩展”,而不是“修改”。当你发现自己在多个函数中重复相同的代码模式时,考虑使用装饰器来抽象这些模式。

最后,建议在实际项目中多练习使用装饰器,从简单的日志记录开始,逐步尝试更复杂的场景。随着经验的积累,你会发现装饰器是 Python 编程中不可或缺的利器。