什么是装饰器?

装饰器(Decorator)是Python中一种强大的语法特性,它允许在不修改原有函数代码的情况下,动态地改变函数的行为。装饰器本质上是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。这种设计模式遵循了开闭原则(对扩展开放,对修改关闭),是实现横切关注点(如日志记录、性能监控、权限验证等)的理想工具。

想象一下,你有一台咖啡机(原始函数),现在想给它添加一个自动关机功能。装饰器就像一个智能插头,你只需把咖啡机插进这个插头,就能获得新功能,而无需拆解咖啡机本身。

装饰器的核心原理

要理解装饰器,需要掌握两个Python核心概念:

  1. 函数是一等公民:函数可以作为参数传递,也可以作为返回值
  2. 闭包:内部函数可以访问外部函数的变量
# 基本装饰器示例
def my_decorator(func):
    def wrapper():
        print("函数执行前")
        func()
        print("函数执行后")
    return wrapper

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

say_hello()
# 输出:
# 函数执行前
# Hello!
# 函数执行后

带参数的函数装饰

当被装饰的函数需要参数时,装饰器内部的包装函数需要使用*args和**kwargs来接收参数:

def parameter_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}, 参数: {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"函数返回: {result}")
        return result
    return wrapper

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

print(add(3, 5))
# 输出:
# 调用函数: add, 参数: (3, 5), {}
# 函数返回: 8
# 8

类装饰器

除了函数装饰器,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"函数 {self.func.__name__} 被调用了 {self.num_calls} 次")
        return self.func(*args, **kwargs)

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

say_hello()
say_hello()
# 输出:
# 函数 say_hello 被调用了 1 次
# Hello!
# 函数 say_hello 被调用了 2 次
# Hello!

带参数的装饰器

有时我们需要给装饰器本身传递参数,这需要创建一个三层嵌套函数:

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("Alice")
# 输出:
# Hello Alice!
# Hello Alice!
# Hello Alice!

装饰器的常见应用场景

  1. 日志记录
def log_execution(func):
    import logging
    logging.basicConfig(level=logging.INFO)
    
    def wrapper(*args, **kwargs):
        logging.info(f"执行函数 {func.__name__},参数: {args}, {kwargs}")
        try:
            result = func(*args, **kwargs)
            logging.info(f"函数 {func.__name__} 执行成功,结果: {result}")
            return result
        except Exception as e:
            logging.error(f"函数 {func.__name__} 执行出错: {str(e)}")
            raise
    return wrapper

@log_execution
def divide(a, b):
    return a / b

divide(10, 2)
  1. 性能计时
import time

def timer(func):
    import functools
    @functools.wraps(func)  # 保留原函数的元信息
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} 执行耗时: {end - start:.4f}秒")
        return result
    return wrapper

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

slow_function()
  1. 权限验证
def requires_admin(func):
    def wrapper(user, *args, **kwargs):
        if user.get("role") != "admin":
            raise PermissionError("需要管理员权限")
        return func(user, *args, **kwargs)
    return wrapper

@requires_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

内置装饰器

Python提供了一些内置装饰器:

  • @property: 将方法转换为属性
  • @classmethod: 类方法
  • @staticmethod: 静态方法
  • @functools.wraps: 保留被装饰函数的元信息
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("半径必须为正数")
        self._radius = value
    
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.radius)  # 5
c.radius = 10
print(c.area)    # 314.159

装饰器的高级技巧

  1. 多个装饰器堆叠使用
def make_bold(func):
    def wrapper():
        return f"<b>{func()}</b>"
    return wrapper

def make_italic(func):
    def wrapper():
        return f"<i>{func()}</i>"
    return wrapper

@make_bold
@make_italic
def greet():
    return "Hello!"

print(greet())  # <b><i>Hello!</i></b>
  1. 使用类作为装饰器
class Memoize:
    def __init__(self, func):
        self.func = func
        self.cache = {}
    
    def __call__(self, *args):
        if args in self.cache:
            return self.cache[args]
        result = self.func(*args)
        self.cache[args] = result
        return result

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

print(fibonacci(10))  # 55

装饰器的最佳实践

  1. 使用functools.wraps
from functools import wraps

def debug(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@debug
def add(a, b):
    """计算两个数的和"""
    return a + b

print(add.__name__)  # 输出"add"而不是"wrapper"
print(add.__doc__)   # 输出"计算两个数的和"
  1. 保持装饰器简单 每个装饰器应该只做一件事,遵循单一职责原则。

  2. 考虑装饰器的执行顺序 装饰器从下往上执行(靠近函数的先执行),从上往下包装(远离函数的最后包装)。

总结

装饰器是Python中非常强大的工具,它能:

  • 增强函数功能而不修改原代码
  • 保持代码的DRY(Don’t Repeat Yourself)原则
  • 提高代码的可读性和可维护性

掌握装饰器需要理解函数作为一等公民、闭包等概念。从简单的函数装饰器开始,逐步学习带参数的装饰器、类装饰器,最后探索装饰器的高级应用。记住,装饰器应该用于增强函数,而不是改变函数的核心逻辑。

通过合理使用装饰器,你可以创建出更加模块化、可重用的代码,大大提高开发效率。