Python 装饰器:从入门到精通

一、简介

在 Python 编程中,装饰器(Decorator)是一种强大的语法糖,它允许开发者在不修改原函数代码的情况下,动态增强函数功能。这种设计模式广泛应用于日志记录、权限验证、性能测试和缓存等场景,是 Python 元编程的重要组成部分。本文将从基础概念出发,逐步深入探讨装饰器的各种应用技巧。

二、装饰器基础原理

2.1 函数作为一等公民

在 Python 中,函数是一等公民(First-Class Citizen),这意味着它们可以:

    作为变量赋值

    作为参数传递

    作为返回值返回

    存储在数据结构中

以下示例展示了函数作为变量和参数的用法:

    def greet(name):

        return f"Hello, {name}!"


    # 将函数赋值给变量

    hello = greet


    # 函数作为参数传递

    def call_function(func, arg):

        return func(arg)


    print(call_function(hello, "Alice"))  # 输出: Hello, Alice!

2.2 闭包(Closure)

闭包是装饰器的核心机制,它指的是一个函数可以访问并记住其外部作用域中的变量,即使外部函数已经执行完毕。

    def outer(x):

        def inner(y):

            return x + y  # 闭包捕获了外部变量x

        return inner


    add_five = outer(5)

    print(add_five(3))  # 输出: 8

2.3 简单装饰器实现

装饰器本质上是一个高阶函数,它接收一个函数作为输入,并返回一个新的函数。以下是一个计算函数执行时间的装饰器示例:

    import time


    def timer_decorator(func):

        def wrapper(*args, **kwargs):

            start_time = time.time()

            result = func(*args, **kwargs)  # 执行原函数

            end_time = time.time()

            print(f"函数 {func.__name__} 执行耗时: {end_time - start_time:.4f} 秒")

            return result

        return wrapper


    @timer_decorator

    def calculate_sum(n):

        return sum(range(n+1))


    print(calculate_sum(1000000))

    # 输出:

    # 函数 calculate_sum 执行耗时: 0.0520 秒

    # 500000500000

这个装饰器的工作原理如下:

    timer_decorator 接收 calculate_sum 函数作为参数

    内部定义了 wrapper 函数来包裹原函数

    返回 wrapper 函数替代原函数

    @timer_decorator 语法糖等价于 calculate_sum = timer_decorator(calculate_sum)

三、装饰器进阶用法

3.1 带参数的装饰器

有时我们需要为装饰器传递额外参数,这可以通过创建一个三层嵌套函数实现:

    def repeat(n):

        def decorator(func):

            def wrapper(*args, **kwargs):

                result = None

                for _ in range(n):

                    result = func(*args, **kwargs)

                return result

            return wrapper

        return decorator


    @repeat(3)

    def say_hi():

        print("Hi!")


    say_hi()  # 输出三次 Hi!

3.2 类装饰器

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

            return self.func(*args, **kwargs)


    @CountCalls

    def say_hello():

        print("Hello!")


    say_hello()  # 输出: 第 1 次调用 say_hello \n Hello!

    say_hello()  # 输出: 第 2 次调用 say_hello \n Hello!

3.3 保留函数元信息

当使用装饰器时,原函数的元信息(如 __name__、__doc__)会被覆盖。可以使用 functools.wraps 来保留这些信息:

    import functools


    def debug(func):

        @functools.wraps(func)  # 保留原函数元信息

        def wrapper(*args, **kwargs):

            print(f"调用 {func.__name__}({args}, {kwargs})")

            result = func(*args, **kwargs)

            print(f"{func.__name__} 返回 {result}")

            return result

        return wrapper


    @debug

    def add(a, b):

        """返回两数之和"""

        return a + b


    print(add(3, 5))

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

    print(add.__doc__)  # 输出: 返回两数之和

3.4 多个装饰器叠加

多个装饰器可以叠加使用,它们会按照从下到上的顺序依次应用:

    def make_bold(func):

        def wrapper(*args, **kwargs):

            return f"<b>{func(*args, **kwargs)}</b>"

        return wrapper


    def make_italic(func):

        def wrapper(*args, **kwargs):

            return f"<i>{func(*args, **kwargs)}</i>"

        return wrapper


    @make_bold

    @make_italic

    def greet(name):

        return f"Hello, {name}!"


    print(greet("Alice"))  # 输出: <b><i>Hello, Alice!</i></b>

四、实战应用案例

4.1 日志记录

自动记录函数调用和返回值:

    def log_decorator(func):

        def wrapper(*args, **kwargs):

            result = func(*args, **kwargs)

            with open('app.log', 'a') as f:

                f.write(f"{func.__name__}({args}, {kwargs}) -> {result}\n")

            return result

        return wrapper


    @log_decorator

    def calculate(a, b):

        return a * b

4.2 权限验证

在执行敏感操作前验证用户权限:

    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, username):

        print(f"删除用户 {username}")

4.3 缓存机制

使用 functools.lru_cache 缓存函数结果:

    import functools


    @functools.lru_cache(maxsize=128)

    def fibonacci(n):

        if n <= 1:

            return n

        return fibonacci(n-1) + fibonacci(n-2)


    print(fibonacci(50))  # 第一次计算较慢,后续调用将直接使用缓存结果

4.4 性能测试

统计函数执行时间并生成报告:

    import time

    import functools


    def performance_report(func):

        @functools.wraps(func)

        def wrapper(*args, **kwargs):

            start_time = time.perf_counter()

            result = func(*args, **kwargs)

            end_time = time.perf_counter()

            print(f"{func.__name__} 执行时间: {end_time - start_time:.6f} 秒")

            return result

        return wrapper


    @performance_report

    def process_data(data):

        # 模拟数据处理

        time.sleep(1)

        return len(data)

五、装饰器高级话题

5.1 装饰器类的参数化

结合类装饰器和参数化装饰器:

    class Retry:

        def __init__(self, max_attempts=3):

            self.max_attempts = max_attempts


        def __call__(self, func):

            def wrapper(*args, **kwargs):

                attempts = 0

                while attempts < self.max_attempts:

                    try:

                        return func(*args, **kwargs)

                    except Exception as e:

                        attempts += 1

                        print(f"尝试 {attempts}/{self.max_attempts} 失败: {e}")

                raise Exception("达到最大重试次数")

            return wrapper


    @Retry(max_attempts=5)

    def connect_to_database():

        # 模拟数据库连接,可能会失败

        import random

        if random.random() < 0.8:

            raise ConnectionError("连接失败")

        return "连接成功"

5.2 装饰器与异步函数

异步函数的装饰器需要使用 async def 和 await:

    import asyncio

    import time


    def async_timer(func):

        async def wrapper(*args, **kwargs):

            start_time = time.time()

            result = await func(*args, **kwargs)

            end_time = time.time()

            print(f"异步函数 {func.__name__} 执行耗时: {end_time - start_time:.4f} 秒")

            return result

        return wrapper


    @async_timer

    async def fetch_data(url):

        await asyncio.sleep(1)  # 模拟网络请求

        return {"data": "example"}

5.3 装饰器与类方法

装饰器也可以应用于类中的方法:

    def debug_method(func):

        def wrapper(self, *args, **kwargs):

            print(f"调用方法 {func.__name__} 来自实例 {self}")

            return func(self, *args, **kwargs)

        return wrapper


    class MyClass:

        @debug_method

        def method(self, x):

            return x * 2

六、总结

装饰器是 Python 中一种非常灵活且强大的工具,它允许我们在不修改原有代码的情况下增强函数或类的功能。通过理解装饰器的基本原理(函数作为一等公民和闭包),我们可以创建出各种实用的装饰器,用于日志记录、权限验证、缓存、性能测试等场景。

在实际应用中,我们需要注意以下几点:

    使用 functools.wraps 保留原函数元信息

    理解装饰器的执行顺序(从下到上)

    合理设计带参数的装饰器

    注意异步函数装饰器的特殊性

掌握装饰器是 Python 程序员进阶的关键一步,它不仅能让你的代码更加简洁优雅,还能提高代码的复用性和可维护性。希望通过本文的介绍,你对 Python 装饰器有了更深入的理解和掌握。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容