文章目录

  • 0. 引言
  • 1. 什么是装饰器?
  • 2. 装饰器的基本语法
  • 3. 装饰器的工作原理
  • 4. 常见装饰器应用场景
  • 4.1. 日志记录
  • 4.2. 权限校验
  • 4.3. 缓存
  • 5. 多重装饰器的执行顺序
  • 6. 装饰器的高级用法
  • 6.1. 带参数的装饰器
  • 6.2. 使用 `functools.wraps`
  • 6.3. 类装饰器
  • 7. 图示说明
  • 7.1. 单一装饰器的执行流程
  • 2. 多重装饰器的执行流程
  • 3. 带参数装饰器的执行流程
  • 总结
  • 8 参考资料
  • 0. 引言

    Python装饰器(Decorator) 不仅可以让你的代码更加简洁、可读,还能有效地实现功能的复用和扩展。本文将带你深入了解Python装饰器的概念、原理及其应用。

    1. 什么是装饰器?

    装饰器是一种高阶函数,它接受一个函数作为参数,并返回一个新的函数。通过装饰器,我们可以在不修改原函数代码的前提下,动态地为其添加额外的功能。
    简单来说,装饰器就是函数的包装器,它可以在函数执行前后添加一些操作。

    2. 装饰器的基本语法

    在Python中,装饰器通常使用 @ 符号来应用于函数或类。下面是一个简单的装饰器示例:

    def my_decorator(func):
        def wrapper():
            print("函数执行前的操作")
            func()
            print("函数执行后的操作")
        return wrapper
    
    @my_decorator
    def say_hello():
        print("Hello!")
    
    say_hello()
    

    输出:

    函数执行前的操作
    Hello!
    函数执行后的操作
    

    在这个例子中:

  • my_decorator 是一个装饰器,它接受一个函数 func 作为参数。
  • wrapper 是一个内部函数,它在调用 func 前后添加了额外的打印操作。
  • @my_decoratorsay_hello 函数传递给装饰器,并将返回的 wrapper 函数重新赋值给 say_hello
  • 因此,当我们调用 say_hello() 时,实际执行的是 wrapper() 函数。

    3. 装饰器的工作原理

    装饰器的核心在于闭包高阶函数。让我们通过一个更详细的示例来理解装饰器的工作机制。

    def decorator(func):
        print("装饰器被调用")
        def wrapper(*args, **kwargs):
            print("在函数执行前")
            result = func(*args, **kwargs)
            print("在函数执行后")
            return result
        return wrapper
    
    @decorator
    def add(a, b):
        print(f"执行加法: {a} + {b}")
        return a + b
    
    result = add(3, 5)
    print(f"结果是: {result}")
    

    输出:

    装饰器被调用
    在函数执行前
    执行加法: 3 + 5
    在函数执行后
    结果是: 8
    

    工作流程图示:

    +------------------+
    | @decorator        |
    | 装饰器被调用        |
    | add 函数被传入      |
    +---------+--------+
              |
              v
    +---------+--------+
    | 返回 wrapper 函数 |
    +---------+--------+
              |
              v
    +---------+--------+
    | 调用 add(3, 5)   | ---> 实际上调用的是 wrapper(3, 5)
    +---------+--------+
              |
              v
    +---------+--------+
    | 打印 "在函数执行前"|
    | 调用原始 add    |
    | 打印 "执行加法:3 +5"|
    | 打印 "在函数执行后"|
    +---------+--------+
              |
              v
    +---------+--------+
    | 返回结果 8        |
    +------------------+
    

    解释:

  • 当Python解释器遇到 @decorator 时,它会先调用 decorator(add)
  • decorator 函数在执行时首先打印“装饰器被调用”。
  • decorator 返回了 wrapper 函数,因此 add 函数被替换为 wrapper
  • 当调用 add(3, 5) 时,实际上调用的是 wrapper(3, 5),它在执行 func(3, 5)(即原始的 add 函数)前后添加了打印操作。
  • 4. 常见装饰器应用场景

    装饰器在实际开发中有着广泛的应用,以下是几个常见的使用场景:

    4.1. 日志记录

    记录函数的调用信息、参数、返回值等,有助于调试和监控。

    def log_decorator(func):
        def wrapper(*args, **kwargs):
            print(f"调用函数 {func.__name__},参数: {args}, {kwargs}")
            result = func(*args, **kwargs)
            print(f"函数 {func.__name__} 返回: {result}")
            return result
        return wrapper
    
    @log_decorator
    def multiply(a, b):
        return a * b
    
    multiply(4, 5)
    

    输出:

    调用函数 multiply,参数: (4, 5), {}
    函数 multiply 返回: 20
    

    4.2. 权限校验

    在函数执行前进行权限检查,确保用户有权限执行该操作。

    def requires_permission(permission):
        def decorator(func):
            def wrapper(*args, **kwargs):
                if not user_has_permission(permission):
                    raise PermissionError("没有权限执行此操作")
                return func(*args, **kwargs)
            return wrapper
        return decorator
    
    @requires_permission('admin')
    def delete_user(user_id):
        print(f"删除用户 {user_id}")
    

    4.3. 缓存

    缓存函数的计算结果,避免重复计算,提高性能。

    def cache_decorator(func):
        cache = {}
        def wrapper(*args):
            if args in cache:
                print("使用缓存")
                return cache[args]
            result = func(*args)
            cache[args] = result
            return result
        return wrapper
    
    @cache_decorator
    def fibonacci(n):
        if n <= 1:
            return n
        return fibonacci(n-1) + fibonacci(n-2)
    
    print(fibonacci(5))
    

    5. 多重装饰器的执行顺序

    当一个函数被多个装饰器装饰时,装饰器的执行顺序可能会让人感到困惑。下面通过一个示例来说明多重装饰器的执行顺序。

    def decorator_a(func):
        print("装饰器 A 被调用")
        def wrapper(*args, **kwargs):
            print("装饰器 A 在函数执行前")
            result = func(*args, **kwargs)
            print("装饰器 A 在函数执行后")
            return result
        return wrapper
    
    def decorator_b(func):
        print("装饰器 B 被调用")
        def wrapper(*args, **kwargs):
            print("装饰器 B 在函数执行前")
            result = func(*args, **kwargs)
            print("装饰器 B 在函数执行后")
            return result
        return wrapper
    
    @decorator_a
    @decorator_b
    def greet(name):
        print(f"Hello, {name}!")
    
    greet("Alice")
    

    输出:

    装饰器 A 被调用
    装饰器 B 被调用
    装饰器 A 在函数执行前
    装饰器 B 在函数执行前
    Hello, Alice!
    装饰器 B 在函数执行后
    装饰器 A 在函数执行后
    

    执行顺序图示:

    装饰器应用阶段:
    +-----------------+       +-----------------+
    | decorator_a     |       | decorator_b     |
    | 调用 decorator_a |       | 调用 decorator_b |
    +--------+--------+       +--------+--------+
             |                         |
             v                         v
    +--------+--------+       +--------+--------+
    | 返回 wrapper_a   |       | 返回 wrapper_b   |
    +--------+--------+       +--------+--------+
             |                         |
             +----------> greet <-------+
                      指向 wrapper_a
                      
    函数调用阶段:
    +-----------------+
    | 调用 greet("Alice") |
    +--------+--------+
             |
             v
    +--------+--------+
    | wrapper_a       |
    | 打印 "装饰器 A 在函数执行前" |
    | 调用 wrapper_b  |
    +--------+--------+
             |
             v
    +--------+--------+
    | wrapper_b       |
    | 打印 "装饰器 B 在函数执行前" |
    | 调用 greet ("Hello, Alice!") |
    | 打印 "装饰器 B 在函数执行后" |
    +--------+--------+
             |
             v
    +--------+--------+
    | wrapper_a       |
    | 打印 "装饰器 A 在函数执行后" |
    +-----------------+
    

    解释:

  • 装饰器的应用顺序是自下而上

  • 首先,greet 函数被 decorator_b 装饰,生成 wrapper_b
  • 然后,wrapper_bdecorator_a 装饰,生成 wrapper_a
  • 最终,greet 指向 wrapper_a
  • 函数调用的执行顺序是自上而下

  • 调用 greet("Alice") 实际上调用的是 wrapper_a("Alice")
  • wrapper_a 打印“装饰器 A 在函数执行前”,然后调用 wrapper_b("Alice")
  • wrapper_b 打印“装饰器 B 在函数执行前”,然后调用原始的 greet("Alice")
  • 原始的 greet 打印“Hello, Alice!”。
  • 然后,wrapper_b 打印“装饰器 B 在函数执行后”。
  • 最后,wrapper_a 打印“装饰器 A 在函数执行后”。
  • 6. 装饰器的高级用法

    6.1. 带参数的装饰器

    有时候,装饰器本身需要接受参数,这时需要使用三层嵌套函数

    def repeat(num_times):
        def decorator(func):
            def wrapper(*args, **kwargs):
                for _ in range(num_times):
                    result = func(*args, **kwargs)
                return result
            return wrapper
        return decorator
    
    @repeat(num_times=3)
    def say(message):
        print(message)
    
    say("Hello!")
    

    输出:

    Hello!
    Hello!
    Hello!
    

    6.2. 使用 functools.wraps

    在装饰器中,使用 functools.wraps 可以保留原函数的元数据,如函数名、文档字符串等。

    import functools
    
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print("调用前")
            return func(*args, **kwargs)
        return wrapper
    
    @my_decorator
    def example():
        """这是一个示例函数"""
        print("示例函数执行")
    
    print(example.__name__)  # 输出: example
    print(example.__doc__)   # 输出: 这是一个示例函数
    

    6.3. 类装饰器

    装饰器不仅可以用于函数,也可以用于类。

    def class_decorator(cls):
        class WrappedClass:
            def __init__(self, *args, **kwargs):
                self.wrapped_instance = cls(*args, **kwargs)
            
            def __getattr__(self, attr):
                return getattr(self.wrapped_instance, attr)
            
            def new_method(self):
                print("这是新添加的方法")
        
        return WrappedClass
    
    @class_decorator
    class MyClass:
        def method(self):
            print("原始方法")
    
    obj = MyClass()
    obj.method()
    obj.new_method()
    

    输出:

    原始方法
    这是新添加的方法
    

    7. 图示说明

    为了更直观地理解装饰器的工作原理及其执行顺序,下面通过几张示意图来辅助说明。

    7.1. 单一装饰器的执行流程

    示意图:

    装饰器应用阶段:
    +-----------------+
    | Original Func   |  (被装饰的函数)
    +--------+--------+
             |
             v
    +--------+--------+
    | Decorator       |  (装饰器函数)
    | 返回 Wrapper    |
    +--------+--------+
             |
             v
    +--------+--------+
    | Wrapper Func    |  (包装后的函数)
    +--------+--------+
    
    函数调用阶段:
    +-----------------+
    | 调用 Wrapper    |
    +--------+--------+
             |
             v
    +--------+--------+
    | 执行装饰器前操作 |
    +--------+--------+
             |
             v
    +--------+--------+
    | 执行原始函数    |
    +--------+--------+
             |
             v
    +--------+--------+
    | 执行装饰器后操作 |
    +-----------------+
    

    解释:

    1. 装饰器应用阶段:原始函数通过装饰器包装,生成一个新的包装函数。
    2. 函数调用阶段:调用包装函数时,先执行装饰器前的操作,再执行原始函数,最后执行装饰器后的操作。

    2. 多重装饰器的执行流程

    示意图:

    装饰器应用阶段:
    +-----------------+       +-----------------+
    | Original Func   |       | Decorator B     |
    +--------+--------+       +--------+--------+
             |                         |
             v                         v
    +--------+--------+       +--------+--------+
    | Decorator A     |       | 返回 Wrapper B   |
    | 返回 Wrapper A  |       +-----------------+
    +--------+--------+
             |
             v
    +--------+--------+
    | Wrapper A       |
    +-----------------+
    
    函数调用阶段:
    +-----------------+
    | 调用 Wrapper A  |
    +--------+--------+
             |
             v
    +--------+--------+
    | 执行 Decorator A 前操作 |
    +--------+--------+
             |
             v
    +--------+--------+
    | 调用 Wrapper B  |
    +--------+--------+
             |
             v
    +--------+--------+
    | 执行 Decorator B 前操作 |
    +--------+--------+
             |
             v
    +--------+--------+
    | 执行原始函数    |
    +--------+--------+
             |
             v
    +--------+--------+
    | 执行 Decorator B 后操作 |
    +--------+--------+
             |
             v
    +--------+--------+
    | 执行 Decorator A 后操作 |
    +-----------------+
    

    解释:

    1. 装饰器应用阶段

    2. 原始函数首先被 Decorator B 装饰,生成 Wrapper B
    3. 然后,Wrapper BDecorator A 装饰,生成 Wrapper A
    4. 最终,函数指向 Wrapper A
    5. 函数调用阶段

    6. 调用 Wrapper A,执行 Decorator A 的前置操作。
    7. Wrapper A 调用 Wrapper B,执行 Decorator B 的前置操作。
    8. Wrapper B 调用原始函数。
    9. 执行 Decorator B 的后置操作。
    10. 执行 Decorator A 的后置操作。

    3. 带参数装饰器的执行流程

    示意图:

    装饰器应用阶段:
    +-----------------+
    | repeat(num_times=3) |
    +--------+--------+
             |
             v
    +--------+--------+
    | Decorator       |
    | 返回 Wrapper     |
    +--------+--------+
             |
             v
    +--------+--------+
    | 原始函数         |
    +--------+--------+
    
    函数调用阶段:
    +-----------------+
    | 调用 Wrapper    |
    +--------+--------+
             |
             v
    +--------+--------+
    | 重复调用原始函数 |
    | 3 次            |
    +-----------------+
    

    解释:

    1. 装饰器应用阶段:带参数的装饰器 repeat 接受参数 num_times=3,返回装饰器函数 decorator,然后 decorator 返回 wrapper 函数。
    2. 函数调用阶段:调用 wrapper 时,根据 num_times 的值,重复调用原始函数 3 次。

    总结

    装饰器通过函数包装器的方式,允许开发者在不修改原函数代码的前提下,为其添加额外的功能。

    8 参考资料

  • Python 官方文档 – 装饰器
  • Python Decorators 101
  • Functools 模块
  • 作者:橘色的喵

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python 装饰器使用详解

    发表回复