引言

Python装饰器(Decorator)是Python语言中一个强大而优雅的特性,它允许我们在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数并返回一个新的函数。这种设计模式体现了软件工程中的”开闭原则”——对扩展开放,对修改关闭。

装饰器的语法使用@符号,这使得代码更加简洁和易读。通过装饰器,我们可以轻松实现日志记录、性能测试、权限验证、缓存等通用功能,从而避免代码重复,提高代码的可维护性。

装饰器的基本概念

什么是装饰器

装饰器是一个可调用的对象(如函数),它接收一个函数作为输入,并返回一个新的函数。这个新函数通常会在原始函数的前后添加一些额外的逻辑。

最简单的装饰器示例

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

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

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

输出结果:

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

装饰器的工作原理

当我们使用@my_decorator语法时,Python实际上执行了以下操作:

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

say_hello = my_decorator(say_hello)

带参数的函数装饰器

处理带参数的函数

当被装饰的函数带有参数时,我们需要在包装函数中也接受相应的参数:

def greet_decorator(func):
    def wrapper(*args, **kwargs):
        print("准备问候...")
        result = func(*args, **kwargs)
        print("问候完成!")
        return result
    return wrapper

@greet_decorator
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")
greet("Bob", "Hi")

输出:

准备问候...
Hello, Alice!
问候完成!
准备问候...
Hi, Bob!
问候完成!

带参数的装饰器

有时我们需要为装饰器本身传递参数,这需要创建一个返回装饰器的函数:

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(times=3)
def greet(name):
    print(f"Hello, {name}!")

greet("World")

输出:

Hello, World!
Hello, World!
Hello, World!

装饰器的实际应用场景

1. 日志记录

import logging
from functools import wraps

def log_execution(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        logging.basicConfig(level=logging.INFO)
        logger = logging.getLogger(func.__name__)
        logger.info(f"开始执行函数: {func.__name__}")
        try:
            result = func(*args, **kwargs)
            logger.info(f"函数执行成功: {func.__name__}")
            return result
        except Exception as e:
            logger.error(f"函数执行失败: {func.__name__}, 错误: {e}")
            raise
    return wrapper

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

# 测试
print(divide(10, 2))
# print(divide(10, 0))  # 这会触发错误日志

2. 性能测试

import time
from functools import wraps

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

@timing
def slow_function():
    time.sleep(1)
    return "完成"

slow_function()

3. 权限验证

from functools import wraps

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 = {"name": "Alice", "role": "admin"}
user = {"name": "Bob", "role": "user"}

delete_user(admin, "Charlie")  # 成功
# delete_user(user, "Charlie")  # 抛出PermissionError

4. 缓存/记忆化

from functools import wraps

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))  # 快速计算,因为使用了缓存

类装饰器

基本类装饰器

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

输出:

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

带参数的类装饰器

class Retry:
    def __init__(self, max_retries=3):
        self.max_retries = max_retries

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(self.max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == self.max_retries - 1:
                        raise
                    print(f"尝试 {attempt + 1} 失败,重试...")
            return None
        return wrapper

@Retry(max_retries=3)
def unstable_function():
    import random
    if random.random() < 0.7:
        raise ValueError("随机失败")
    return "成功"

# 多次运行可能得到不同结果
print(unstable_function())

内置装饰器

@property

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius ** 2

    @property
    def perimeter(self):
        return 2 * 3.14159 * self.radius

circle = Circle(5)
print(f"半径: {circle.radius}")
print(f"面积: {circle.area}")
print(f"周长: {circle.perimeter}")

# 可以像访问属性一样使用,不需要括号

@classmethod 和 @staticmethod

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)

    @staticmethod
    def is_adult(age):
        return age >= 18

# 使用类方法
person1 = Person("Alice", 25)
person2 = Person.from_birth_year("Bob", 2000)

print(person1.name, person1.age)
print(person2.name, person2.age)

# 使用静态方法
print(Person.is_adult(20))  # True
print(Person.is_adult(15))  # False

高级装饰器技术

装饰器堆叠(多个装饰器一起使用)

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>

使用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 (如果没有@wraps会输出wrapper)
print(add.__doc__)   # 输出: 计算两个数的和

装饰器类参数

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_percentage(value):
    print(f"设置百分比为: {value}%")

set_percentage(50)  # 正常
# set_percentage(150)  # 抛出ValueError

装饰器的最佳实践

1. 保持装饰器的单一职责

# 不好的做法:一个装饰器做太多事情
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        # 日志
        print("日志...")
        # 验证
        if not check_permission():
            raise PermissionError()
        # 计时
        start = time.time()
        result = func(*args, **kwargs)
        print(f"耗时: {time.time() - start}")
        return result
    return wrapper

# 好的做法:拆分成多个装饰器
def log_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

def timing_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"耗时: {time.time() - start}")
        return result
    return wrapper

2. 文档和类型提示

from typing import Callable, Any
from functools import wraps

def validate_type(*expected_types: type) -> Callable:
    """
    验证函数参数类型的装饰器
    
    Args:
        *expected_types: 期望的参数类型
        
    Returns:
        Callable: 装饰后的函数
    """
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            if len(args) != len(expected_types):
                raise ValueError("参数数量不匹配")
            
            for arg, expected in zip(args, expected_types):
                if not isinstance(arg, expected):
                    raise TypeError(f"期望 {expected}, 得到 {type(arg)}")
            
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_type(int, int)
def multiply(a: int, b: int) -> int:
    """两个整数相乘"""
    return a * b

print(multiply(3, 4))  # 12
# multiply("3", 4)  # TypeError

3. 装饰器的性能考虑

import time
from functools import wraps

# 高性能缓存装饰器(使用LRU缓存)
from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_calculation(n):
    """模拟耗时计算"""
    time.sleep(0.1)
    return n * n

# 第一次调用会执行计算
print(expensive_calculation(5))
# 第二次调用直接返回缓存结果
print(expensive_calculation(5))

# 查看缓存信息
print(expensive_calculation.cache_info())

装饰器的调试技巧

1. 使用namedoc属性

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
print(example.__doc__)   # 这是一个示例函数

2. 装饰器调试示例

def verbose_decorator(func):
    """详细的调试装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"\n=== 调试信息 ===")
        print(f"函数名: {func.__name__}")
        print(f"位置参数: {args}")
        print(f"关键字参数: {kwargs}")
        print(f"函数文档: {func.__doc__}")
        result = func(*args, **kwargs)
        print(f"返回值: {result}")
        print(f"=== 调试结束 ===\n")
        return result
    return wrapper

@verbose_decorator
def complex_calculation(x, y, operation="add"):
    """执行复杂的数学运算"""
    if operation == "add":
        return x + y
    elif operation == "multiply":
        return x * y

complex_calculation(5, 3, operation="multiply")

实际项目中的装饰器应用

Web框架中的路由装饰器

class Router:
    def __init__(self):
        self.routes = {}

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

    def handle_request(self, path):
        if path in self.routes:
            return self.routes[path]()
        else:
            return "404 Not Found"

router = Router()

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

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

# 模拟请求
print(router.handle_request("/"))        # Welcome to Home Page
print(router.handle_request("/about"))   # About Us
print(router.handle_request("/contact")) # 404 Not Found

数据库连接管理

from functools import wraps
import sqlite3

def with_db_connection(db_path):
    """数据库连接装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            conn = sqlite3.connect(db_path)
            try:
                result = func(conn, *args, **kwargs)
                conn.commit()
                return result
            finally:
                conn.close()
        return wrapper
    return decorator

@with_db_connection("example.db")
def create_table(conn):
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            email TEXT NOT NULL
        )
    """)
    print("表创建成功")

@with_db_connection("example.db")
def insert_user(conn, name, email):
    cursor = conn.cursor()
    cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", (name, email))
    print(f"用户 {name} 插入成功")

# 使用
create_table()
insert_user("Alice", "alice@example.com")

总结

装饰器是Python中一个非常强大和优雅的特性,它允许我们以声明式的方式为函数添加功能,而无需修改函数的内部实现。通过本文的详细讲解和丰富的示例,我们学习了:

  1. 基础概念:装饰器的本质是高阶函数,使用@语法糖简化使用
  2. 基本用法:如何创建简单的装饰器,处理带参数的函数
  3. 高级特性:带参数的装饰器、类装饰器、内置装饰器
  4. 实际应用:日志记录、性能测试、权限验证、缓存等场景
  5. 最佳实践:保持单一职责、使用functools.wraps、添加文档和类型提示
  6. 高级技巧:装饰器堆叠、调试技巧、项目中的实际应用

装饰器的核心优势在于:

  • 代码复用:避免重复代码
  • 关注点分离:将核心逻辑与横切关注点(如日志、验证)分离
  • 可读性:使用@语法使代码意图更清晰
  • 灵活性:可以轻松组合和重用

掌握装饰器将大大提升你的Python编程能力,使代码更加优雅、可维护和可扩展。在实际项目中,合理使用装饰器可以显著提高开发效率和代码质量。