什么是装饰器?

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

装饰器的核心思想是遵循开放封闭原则:对扩展开放,对修改封闭。这意味着你可以通过装饰器来扩展代码的功能,而不需要修改原有的代码实现。

基础装饰器的实现

让我们从一个简单的例子开始。假设我们有一个需要记录执行时间的函数:

import time

def timer_decorator(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

@timer_decorator
def calculate_sum(n):
    total = 0
    for i in range(n):
        total += i
    return total

# 使用示例
result = calculate_sum(1000000)
print(f"计算结果: {result}")

在这个例子中:

  1. timer_decorator 是一个装饰器函数
  2. 它接收一个函数 func 作为参数
  3. 返回一个新的函数 wrapper,这个函数在调用原函数前后添加了计时功能
  4. 使用 @timer_decorator 语法将装饰器应用到 calculate_sum 函数上

带参数的装饰器

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

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

@repeat_decorator(3)
def greet(name):
    print(f"你好, {name}!")
    return name

# 使用示例
greet("小明")

输出:

你好, 小明!
你好, 小明!
你好, 小明!

类装饰器

除了函数装饰器,Python还支持类装饰器。类装饰器需要实现 __call__ 方法:

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.func.__name__} 被调用了 {self.call_count} 次")
        return self.func(*args, **kwargs)

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

# 使用示例
say_hello()
say_hello()
say_hello()

输出:

函数 say_hello 被调用了 1 次
Hello!
函数 say_hello 被调用了 2 次
Hello!
函数 say_hello 被调用了 3 次
Hello!

内置装饰器

Python提供了一些内置装饰器,最常用的是:

1. @property

将方法转换为属性:

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("半径必须大于0")
        self._radius = value
    
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

# 使用示例
c = Circle(5)
print(c.radius)  # 5
print(c.area)    # 78.53975
c.radius = 10
print(c.area)    # 314.159

2. @classmethod

将方法转换为类方法:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    @classmethod
    def from_birth_year(cls, name, birth_year):
        from datetime import datetime
        current_year = datetime.now().year
        age = current_year - birth_year
        return cls(name, age)

# 使用示例
p1 = Person("Alice", 25)
p2 = Person.from_birth_year("Bob", 1995)
print(p2.name, p2.age)

3. @staticmethod

将方法转换为静态方法:

class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b
    
    @staticmethod
    def multiply(a, b):
        return a * b

# 使用示例
print(MathUtils.add(3, 5))      # 8
print(MathUtils.multiply(3, 5)) # 15

装饰器的高级应用

1. 多个装饰器的组合

多个装饰器可以堆叠使用,执行顺序是从下往上:

def bold(func):
    def wrapper():
        return "<b>" + func() + "</b>"
    return wrapper

def italic(func):
    def wrapper():
        return "<i>" + func() + "</i>"
    return wrapper

@bold
@italic
def hello():
    return "Hello World"

print(hello())  # <b><i>Hello World</i></b>

2. 使用functools.wraps

为了保留原函数的元信息(如函数名、文档字符串等),应该使用 functools.wraps

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """这是一个包装函数"""
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def example():
    """这是一个示例函数"""
    pass

print(example.__name__)  # example (而不是wrapper)
print(example.__doc__)   # 这是一个示例函数

3. 装饰器的实际应用场景

缓存/记忆化

from functools import wraps
import time

def cache(seconds):
    def decorator(func):
        cache_dict = {}
        @wraps(func)
        def wrapper(*args):
            current_time = time.time()
            # 检查缓存是否存在且未过期
            if args in cache_dict:
                result, timestamp = cache_dict[args]
                if current_time - timestamp < seconds:
                    print("使用缓存结果")
                    return result
            
            # 执行函数并缓存结果
            result = func(*args)
            cache_dict[args] = (result, current_time)
            print("计算新结果")
            return result
        return wrapper
    return decorator

@cache(5)  # 缓存5秒
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 使用示例
print(fibonacci(10))  # 计算新结果
time.sleep(2)
print(fibonacci(10))  # 使用缓存结果
time.sleep(4)
print(fibonacci(10))  # 计算新结果

权限验证

def require_admin(func):
    @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")  # 正常执行
# delete_user(regular_user, "Charlie")  # 抛出PermissionError

重试机制

def retry(max_attempts=3, delay=1):
    import time
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts == max_attempts:
                        raise e
                    print(f"尝试 {attempts}/{max_attempts} 失败,{delay}秒后重试...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_function():
    import random
    if random.random() < 0.7:
        raise Exception("随机失败")
    return "成功"

# 使用示例
try:
    result = unstable_function()
    print(result)
except Exception as e:
    print(f"最终失败: {e}")

装饰器的最佳实践

  1. 保持装饰器简单:每个装饰器应该只做一件事
  2. 使用functools.wraps:保留原函数的元信息
  3. 考虑装饰器的顺序:多个装饰器时注意执行顺序
  4. 为装饰器编写文档:说明其功能和参数
  5. 测试装饰器:确保装饰器在各种情况下都能正常工作

总结

装饰器是Python中非常强大的工具,它可以帮助我们:

  • 在不修改原函数的情况下添加功能
  • 保持代码的简洁和可维护性
  • 实现代码复用
  • 遵循开放封闭原则

通过掌握装饰器,你可以写出更加优雅和Pythonic的代码。从简单的日志记录到复杂的缓存机制,装饰器都能派上用场。记住,装饰器的核心思想是包装和扩展,而不是修改。

在实际项目中,装饰器常用于:

  • 日志记录和性能监控
  • 权限验证和访问控制
  • 缓存和记忆化
  • 输入验证和错误处理
  • 事务管理
  • 路由注册(在Web框架中)

希望这篇文章能帮助你深入理解Python装饰器的概念和应用!