当前位置: 首页 > article >正文

Python 装饰器使用详解

文章目录

    • 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. 装饰器应用阶段

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

    • 调用 Wrapper A,执行 Decorator A 的前置操作。
    • Wrapper A 调用 Wrapper B,执行 Decorator B 的前置操作。
    • Wrapper B 调用原始函数。
    • 执行 Decorator B 的后置操作。
    • 执行 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 模块

http://www.kler.cn/news/313264.html

相关文章:

  • 【Java集合】LinkedList
  • AV1 Bitstream Decoding Process Specification--[5]: 语法结构语义-1
  • LeetCode[中等] 3. 无重复字符的最长子串
  • Java项目实战II基于Java+Spring Boot+MySQL的大型商场应急预案管理系统(源码+数据库+文档)
  • 【原创】java+springboot+mysql科研成果管理系统设计与实现
  • Vue.js 与后端配合:打造强大的现代 Web 应用
  • YoloV9改进策略:BackBone改进|EfficientFormerV2在YoloV9中的创新应用,精度与效率的完美平衡
  • fmql之ubuntu联网
  • 【 ACM独立出版,见刊后1个月检索!!!】第二届通信网络与机器学习国际学术会议(CNML 2024,10月25-27)
  • JS在线加密解密工具
  • 应用性能优化实践(三)减少丢帧卡顿
  • 无线麦克风哪个好,麦克风哪个品牌音质最好,领夹麦克风推荐
  • 可视化工具箱-Visualization Toolkit(VTK)
  • go语言中的数组指针和指针数组的区别详解
  • 【车载开发系列】APQP基本概念
  • IDEA2023.1添加java虚拟机启动参数,打开断言
  • 对接阿里asr和Azure asr
  • KMP算法的实现
  • How can I integrate OpenAI Whisper model into a Kotlin app?
  • 解决 iOS App Tracking Transparency 权限问题
  • windows下,用docker部署xinference,为什么老是提示localhost无法访问?
  • 图说几何学2300年重大错误:附着在直线z上的直线段必是z的一部分
  • WebServer:buffer
  • Intel架构的基本知识
  • 我整理了一些关于【软考备考】的学习资料和大家一起分享、学习一下
  • 《 C++ 修炼全景指南:九 》打破编程瓶颈!掌握二叉搜索树的高效实现与技巧
  • STM32使用 :串口的接收与发送
  • 【RabbitMQ】RabbitMQ 概述
  • 在HTML中添加段落方法
  • 领域驱动DDD三种架构-分层架构、洋葱架构、六边形架构