什么是Python装饰器?

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

装饰器的核心概念

def my_decorator(func):
    def wrapper():
        print("在函数调用前执行")
        func()
        print("在函数调用后执行")
    return wrapper

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

say_hello()

这段代码的输出是:

在函数调用前执行
Hello!
在函数调用后执行

为什么需要装饰器?

装饰器在实际开发中非常有用,主要体现在以下几个方面:

  1. 代码复用:避免在多个函数中重复编写相同的逻辑
  2. 关注点分离:将核心业务逻辑与辅助功能(如日志、性能监控)分离
  3. 非侵入式修改:不修改原有函数代码就能添加新功能

装饰器的常见使用场景

1. 日志记录

def log_execution(func):
    import logging
    logging.basicConfig(level=logging.INFO)
    
    def wrapper(*args, **kwargs):
        logging.info(f"开始执行函数: {func.__name__}")
        result = func(*args, **kwargs)
        logging.info(f"函数执行完成: {func.__name__}")
        return result
    return wrapper

@log_execution
def calculate_sum(a, b):
    return a + b

print(calculate_sum(3, 5))

2. 性能计时

import time

def timer(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 "完成"

slow_function()

3. 权限验证

def require_login(func):
    def wrapper(user, *args, **kwargs):
        if not user.get('is_logged_in'):
            raise PermissionError("用户未登录")
        return func(user, *args, **kwargs)
    return wrapper

@require_login
def view_profile(user):
    print(f"查看用户 {user['name']} 的个人资料")

# 测试
logged_in_user = {'name': 'Alice', 'is_logged_in': True}
logged_out_user = {'name': 'Bob', 'is_logged_in': False}

view_profile(logged_in_user)  # 正常执行
# view_profile(logged_out_user)  # 会抛出PermissionError

带参数的装饰器

有时候我们需要让装饰器本身接受参数,这时需要创建三层嵌套函数:

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

输出:

你好, World!
你好, World!
你好, World!

类装饰器

除了函数装饰器,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 say_hi():
    print("Hi!")

say_hi()
say_hi()
say_hi()

输出:

函数 say_hi 被调用了 1 次
Hi!
函数 say_hi 被调用了 2 次
Hi!
函数 say_hi 被调用了 3 次
Hi!

装饰器的高级用法

1. 保留原函数元信息

使用functools.wraps保留原函数的名称和文档字符串:

from functools import wraps

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

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

print(add.__name__)  # 输出: add
print(add.__doc__)   # 输出: 计算两个数的和

2. 多个装饰器叠加使用

def make_bold(func):
    @wraps(func)
    def wrapper():
        return f"<b>{func()}</b>"
    return wrapper

def make_italic(func):
    @wraps(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>

3. 基于类的装饰器带参数

class ValidateInput:
    def __init__(self, min_val=None, max_val=None):
        self.min_val = min_val
        self.max_val = max_val
    
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for arg in args:
                if self.min_val is not None and arg < self.min_val:
                    raise ValueError(f"参数 {arg} 小于最小值 {self.min_val}")
                if self.max_val is not None and arg > self.max_val:
                    raise ValueError(f"参数 {arg} 大于最大值 {self.max_val}")
            return func(*args, **kwargs)
        return wrapper

@ValidateInput(min_val=0, max_val=100)
def set_age(age):
    print(f"设置年龄为: {age}")

set_age(25)  # 正常执行
# set_age(150)  # 会抛出ValueError

实际项目中的装饰器应用

Web框架中的路由系统

# 简化的路由装饰器实现
route_map = {}

def route(path):
    def decorator(func):
        route_map[path] = func
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    return decorator

@route("/home")
def home():
    return "Welcome to Home Page"

@route("/about")
def about():
    return "About Us"

# 模拟路由请求
def handle_request(path):
    if path in route_map:
        return route_map[path]()
    return "404 Not Found"

print(handle_request("/home"))  # 输出: Welcome to Home Page
print(handle_request("/about")) # 输出: About Us

缓存装饰器

from functools import lru_cache

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

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

print(fibonacci(10))  # 输出: 55

装饰器的注意事项

  1. 调试困难:装饰器会改变函数签名,增加调试复杂度
  2. 执行顺序:多个装饰器时,最靠近函数的装饰器先执行
  3. 性能影响:装饰器会增加函数调用的开销
  4. 文档维护:确保使用@wraps保留原函数信息

总结

Python装饰器是一个强大而灵活的工具,掌握它可以帮助你写出更优雅、更模块化的代码。从简单的日志记录到复杂的Web框架,装饰器无处不在。建议从简单的单层装饰器开始练习,逐步掌握带参数的装饰器和类装饰器,最终能够在实际项目中灵活运用。

记住:装饰器的核心思想是”不修改原有代码的情况下扩展功能”,这是软件工程中”开闭原则”的完美体现。