什么是装饰器?

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

装饰器的核心概念

装饰器基于Python的几个重要特性:

  1. 函数是一等公民:函数可以作为参数传递,也可以作为返回值
  2. 闭包:内部函数可以访问外部函数的变量
  3. 语法糖@decorator语法提供了一种简洁的装饰器应用方式

基础装饰器实现

最简单的装饰器示例

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__}")
        print(f"位置参数: {args}")
        print(f"关键字参数: {kwargs}")
        result = func(*args, **kwargs)
        print(f"函数返回: {result}")
        return result
    return wrapper

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

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

输出:

调用函数: add
位置参数: (3, 5)
关键字参数: {'x': 10}
函数返回: 8
最终结果: 8

装饰器的实际应用场景

1. 计时器装饰器

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():
    time.sleep(1)
    return "完成"

result = slow_function()
print(result)

2. 缓存/记忆化装饰器

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

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

print(f"Fibonacci(10) = {fibonacci(10)}")
print(f"Fibonacci(8) = {fibonacci(8)}")  # 这次会从缓存获取

3. 权限验证装饰器

def require_permission(permission_level):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get('permission') < permission_level:
                raise PermissionError("权限不足")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

# 使用
@require_permission(3)
def delete_database(user):
    print(f"用户 {user['name']} 正在删除数据库...")

# 测试
admin = {'name': 'Alice', 'permission': 5}
guest = {'name': 'Bob', 'permission': 1}

delete_database(admin)  # 成功
try:
    delete_database(guest)  # 失败
except PermissionError as e:
    print(f"错误: {e}")

类装饰器

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

@CountCalls
def example_function():
    print("执行主要功能")

example_function()
example_function()
example_function()

输出:

函数 example_function 被调用了 1 次
执行主要功能
函数 example_function 被调用了 2 次
执行主要功能
函数 example_function 被调用了 3 次
执行主要功能

装饰器的高级用法

1. 多个装饰器的组合

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

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

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

print(greet())  # 输出: <b><i>Hello World</i></b>

2. 带参数的类装饰器

class Repeater:
    def __init__(self, times):
        self.times = times
    
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(self.times):
                result = func(*args, **kwargs)
            return result
        return wrapper

@Repeater(times=3)
def say_hi():
    print("Hi!")

say_hi()

输出:

Hi!
Hi!
Hi!

装饰器的最佳实践

  1. 使用functools.wraps:保留原始函数的元信息(名称、文档字符串等)
  2. 保持装饰器的单一职责:每个装饰器应该只做一件事
  3. 考虑性能影响:装饰器会增加函数调用的开销
  4. 编写清晰的文档:说明装饰器的作用和使用方法
  5. 测试装饰器:确保它们在各种情况下都能正常工作

实战案例:构建一个完整的日志系统

import logging
from datetime import datetime
from functools import wraps

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

def log_execution(level=logging.INFO):
    """记录函数执行日志的装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            logger = logging.getLogger(func.__name__)
            
            # 记录调用信息
            logger.log(level, f"开始执行,参数: {args}, {kwargs}")
            
            try:
                result = func(*args, **kwargs)
                logger.log(level, f"执行成功,返回: {result}")
                return result
            except Exception as e:
                logger.error(f"执行失败: {str(e)}")
                raise
        
        return wrapper
    return decorator

def validate_input(*validations):
    """输入验证装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for i, (arg, validation) in enumerate(zip(args, validations)):
                if not validation(arg):
                    raise ValueError(f"参数 {i} 验证失败: {arg}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 组合使用
@log_execution(logging.INFO)
@validate_input(lambda x: x > 0, lambda y: y > 0)
def divide(a, b):
    return a / b

# 测试
try:
    print(divide(10, 2))
    print(divide(-1, 2))  # 验证失败
except Exception as e:
    print(f"错误: {e}")

总结

装饰器是Python中非常强大和优雅的特性,它们可以帮助我们:

  • 代码复用:避免重复代码
  • 关注点分离:将核心逻辑与横切关注点(如日志、缓存、验证)分离
  • 保持代码整洁:使业务逻辑更清晰

掌握装饰器的使用,将使你的Python代码更加Pythonic,更易于维护和扩展。记住,装饰器的核心思想是”包装”,它让我们能够在不修改原有代码的情况下,为函数添加新的功能。