什么是Python装饰器?

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

装饰器的核心概念

装饰器的工作原理基于Python的函数是一等公民(First-class objects)这一特性。这意味着函数可以像其他对象一样被传递、赋值和返回。

# 一个简单的装饰器示例
def my_decorator(func):
    def wrapper():
        print("函数执行前")
        func()
        print("函数执行后")
    return wrapper

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

# 调用被装饰的函数
say_hello()

输出结果:

函数执行前
Hello!
函数执行后

装饰器的基本语法

1. 使用@符号的装饰器

Python提供了@语法糖来简化装饰器的使用:

def timer_decorator(func):
    import time
    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_decorator
def calculate_sum(n):
    return sum(range(n))

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

2. 带参数的装饰器

有时候我们需要让装饰器本身接受参数:

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

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

greet("Alice")

输出:

你好, Alice!
你好, Alice!
你好, Alice!

装饰器的高级应用

1. 类装饰器

装饰器不仅可以是函数,也可以是类:

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_whee():
    print("Whee!")

say_whee()
say_whee()

2. 多个装饰器的组合

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

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 World"

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

实际应用场景

1. 日志记录装饰器

import logging
from functools import wraps

def log_execution(func):
    @wraps(func)  # 保留原函数的元数据
    def wrapper(*args, **kwargs):
        logging.info(f"开始执行: {func.__name__}")
        try:
            result = func(*args, **kwargs)
            logging.info(f"执行成功: {func.__name__}")
            return result
        except Exception as e:
            logging.error(f"执行失败: {func.__name__}, 错误: {e}")
            raise
    return wrapper

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

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

# 测试
try:
    print(divide(10, 2))
    print(divide(10, 0))
except:
    pass

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

from functools import lru_cache
import time

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

# 测试性能
start = time.time()
result = fibonacci(30)
end = time.time()
print(f"结果: {result}, 耗时: {end - start:.4f}秒")

# 再次调用,速度会快很多
start = time.time()
result = fibonacci(30)
end = time.time()
print(f"再次调用结果: {result}, 耗时: {end - start:.4f}秒")

3. 权限验证装饰器

class User:
    def __init__(self, username, role):
        self.username = username
        self.role = role

def require_role(required_role):
    def decorator(func):
        def wrapper(user, *args, **kwargs):
            if user.role != required_role:
                raise PermissionError(f"需要 {required_role} 权限")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_user(user, target_username):
    print(f"用户 {user.username} 删除了 {target_username}")

# 测试
admin = User("alice", "admin")
guest = User("bob", "guest")

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

装饰器的最佳实践

1. 使用functools.wraps

始终使用@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__)   # 输出: 原始函数的文档字符串

2. 装饰器的参数验证

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

# 使用示例
@validate_input(lambda x: x > 0, lambda y: y > 0)
def calculate_area(width, height):
    return width * height

print(calculate_area(5, 10))  # 成功
try:
    print(calculate_area(-5, 10))  # 失败
except ValueError as e:
    print(f"错误: {e}")

装饰器的调试技巧

1. 检查装饰器是否正确应用

def debug_decorator(func):
    print(f"装饰器应用于函数: {func.__name__}")
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@debug_decorator
def test_function():
    pass

# 输出: 装饰器应用于函数: test_function

2. 使用inspect模块检查装饰器

import inspect

def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def example():
    pass

# 检查包装后的函数
print(inspect.unwrap(example))  # 获取原始函数

常见陷阱和解决方案

1. 装饰器不保留原函数信息

问题:

def bad_decorator(func):
    def wrapper():
        return func()
    return wrapper

@bad_decorator
def my_func():
    """文档字符串"""
    pass

print(my_func.__name__)  # 输出: wrapper (不是my_func)

解决方案:

from functools import wraps

def good_decorator(func):
    @wraps(func)
    def wrapper():
        return func()
    return wrapper

2. 装饰器参数传递错误

# 错误示例
def decorator_with_args(arg1, arg2=None):
    def decorator(func):
        def wrapper():
            print(f"arg1={arg1}, arg2={arg2}")
            return func()
        return wrapper
    return decorator

@decorator_with_args("hello")  # 正确
@decorator_with_args("hello", "world")  # 正确
def my_func():
    pass

总结

装饰器是Python中非常强大和灵活的工具,掌握它们可以让你的代码更加优雅和可维护。记住以下要点:

  1. 始终使用@wraps 保留原函数元数据
  2. 理解执行顺序:多个装饰器从下到上执行
  3. 合理使用参数:带参数的装饰器需要三层嵌套
  4. 保持简单:装饰器应该专注于单一职责
  5. 充分测试:确保装饰器在各种情况下都能正常工作

通过本文的详细示例和解释,你现在应该对Python装饰器有了深入的理解,可以在实际项目中灵活运用它们来提升代码质量。