什么是 Python 装饰器?

装饰器(Decorator)是 Python 中一种强大的语法特性,它允许你在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。

装饰器的核心概念

在深入学习之前,我们需要理解几个关键概念:

  1. 函数是一等公民:在 Python 中,函数可以作为参数传递,也可以作为返回值。
  2. 闭包:装饰器依赖于闭包的概念,即内部函数可以访问外部函数的变量。
  3. 语法糖@ 符号是装饰器的语法糖,它让装饰器的使用更加简洁。

基础装饰器的实现

最简单的装饰器示例

让我们从一个最基础的装饰器开始,它会在函数执行前后打印日志:

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

# 使用装饰器
@logger_decorator
def add(a, b):
    return a + b

# 测试
result = add(3, 5)
print(f"结果: {result}")

输出结果:

开始执行函数: add
函数执行完成: add
结果: 8

带参数的装饰器

有时候我们需要让装饰器本身接受参数,这需要创建一个返回装饰器的函数:

def repeat_decorator(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_decorator(3)
def greet(name):
    print(f"你好, {name}!")
    return f"问候了 {name}"

# 测试
greet("Alice")

输出结果:

第 1 次执行
你好, Alice!
第 2 次执行
你好, Alice!
第 3 次执行
你好, Alice!

装饰器的高级用法

保留原函数的元信息

默认情况下,装饰器会覆盖原函数的 __name____doc__ 等属性。使用 functools.wraps 可以解决这个问题:

import functools

def preserve_info_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        """这是包装函数的文档字符串"""
        print("执行装饰器逻辑")
        return func(*args, **kwargs)
    return wrapper

@preserve_info_decorator
def example_function():
    """这是原函数的文档字符串"""
    return "Hello"

print(f"函数名: {example_function.__name__}")
print(f"文档字符串: {example_function.__doc__}")

输出结果:

函数名: example_function
文档字符串: 这是原函数的文档字符串

类装饰器

除了函数装饰器,Python 还支持类装饰器:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.call_count = 0
    
    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"函数被调用了 {self.call_count} 次")
        return self.func(*args, **kwargs)

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

# 测试
say_hello()
say_hello()
say_hello()

输出结果:

函数被调用了 1 次
函数被调用了 2 次
函数被调用了 3 次

实际应用场景

1. 性能监控装饰器

import time
import functools

def timing_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"函数 {func.__name__} 执行时间: {end_time - start_time:.4f} 秒")
        return result
    return wrapper

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

# 测试
slow_function()

2. 权限验证装饰器

def require_admin(func):
    @functools.wraps(func)
    def wrapper(user, *args, **kwargs):
        if user.get('role') != 'admin':
            raise PermissionError("需要管理员权限")
        return func(user, *args, **kwargs)
    return wrapper

@require_admin
def delete_user(user, target_user):
    print(f"用户 {user['name']} 删除了用户 {target_user}")

# 测试
admin_user = {'name': 'Alice', 'role': 'admin'}
regular_user = {'name': 'Bob', 'role': 'user'}

# 这会正常执行
delete_user(admin_user, "Charlie")

# 这会抛出异常
try:
    delete_user(regular_user, "David")
except PermissionError as e:
    print(f"错误: {e}")

3. 缓存装饰器(记忆化)

def memoize(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args in cache:
            print(f"从缓存中获取结果: {args}")
            return cache[args]
        result = func(*args)
        cache[args] = result
        print(f"计算新结果: {args} = {result}")
        return result
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 测试
print("计算 fibonacci(5):")
print(fibonacci(5))

装饰器的执行顺序

当多个装饰器堆叠使用时,它们的执行顺序是从下往上的:

def decorator1(func):
    def wrapper(*args, **kwargs):
        print("装饰器1 - 前")
        result = func(*args, **kwargs)
        print("装饰器1 - 后")
        return result
    return wrapper

def decorator2(func):
    def wrapper(*args, **kwargs):
        print("装饰器2 - 前")
        result = func(*args, **kwargs)
        print("装饰器2 - 后")
        return result
    return wrapper

@decorator1
@decorator2
def my_function():
    print("执行原函数")

# 测试
my_function()

输出结果:

装饰器1 - 前
装饰器2 - 前
执行原函数
装饰器2 - 后
装饰器1 - 后

常见陷阱和注意事项

1. 装饰器的参数传递问题

def debug_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"参数: args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

@debug_decorator
def complex_function(a, b, c=10, d=20):
    return a + b + c + d

# 正确使用
complex_function(1, 2, c=3, d=4)

2. 类方法的装饰器

class MyClass:
    def __init__(self):
        self.value = 0
    
    @logger_decorator  # 使用之前定义的logger装饰器
    def increment(self, amount):
        self.value += amount
        return self.value

obj = MyClass()
obj.increment(5)

最佳实践

  1. 保持装饰器的单一职责:每个装饰器应该只做一件事
  2. 使用 functools.wraps:始终保留原函数的元信息
  3. 文档化:为装饰器和被装饰的函数提供清晰的文档
  4. 避免过度使用:装饰器很强大,但不是所有问题都需要装饰器
  5. 测试:确保装饰器在各种情况下都能正常工作

总结

Python 装饰器是一个强大而优雅的特性,它能够:

  • 在不修改原函数的情况下添加功能
  • 保持代码的简洁和可读性
  • 实现关注点分离
  • 提高代码的复用性

通过掌握装饰器,你可以写出更加 Pythonic 的代码,让你的程序更加模块化和易于维护。从简单的日志记录到复杂的权限控制,装饰器都能派上用场。记住,好的装饰器应该像一个透明的层,增强函数的功能而不改变其本质。