Python 装饰器使用详解

news/2024/9/21 14:20:18/

文章目录

0. 引言

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

1. 什么是装饰器

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

2. 装饰器的基本语法

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

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. 装饰器的工作原理

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

python">def decorator(func):print("装饰器被调用")def wrapper(*args, **kwargs):print("在函数执行前")result = func(*args, **kwargs)print("在函数执行后")return resultreturn wrapper@decorator
def add(a, b):print(f"执行加法: {a} + {b}")return a + bresult = 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. 日志记录

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

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

输出:

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

4.2. 权限校验

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

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

4.3. 缓存

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

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

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

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

python">def decorator_a(func):print("装饰器 A 被调用")def wrapper(*args, **kwargs):print("装饰器 A 在函数执行前")result = func(*args, **kwargs)print("装饰器 A 在函数执行后")return resultreturn wrapperdef decorator_b(func):print("装饰器 B 被调用")def wrapper(*args, **kwargs):print("装饰器 B 在函数执行前")result = func(*args, **kwargs)print("装饰器 B 在函数执行后")return resultreturn 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. 带参数的装饰器

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

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

输出:

Hello!
Hello!
Hello!

6.2. 使用 functools.wraps

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

python">import functoolsdef 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. 类装饰器

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

python">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 参考资料


http://www.ppmy.cn/news/1528429.html

相关文章

【笔记篇】Davinci Configurator MemIf模块

目录 1 简介1.1 架构概览2 功能描述2.1 内存架构2.2 初始化2.3 主函数2.4 故障处理3 集成3.1 静态文件3.2 动态文件4 API描述5 配置1 简介 本文主要描述了AUTOSAR MemIf模块的功能。 MemIf(内存抽象接口)为 NvM 提供了访问非易失性内存设备的接口。这里主要使用两种类型的非…

视频转音频,分享这六种转换操作

视频转音频&#xff0c;随着多媒体技术的发展&#xff0c;人们越来越频繁地需要将视频中的音频部分提取出来单独使用。无论是为了制作播客、获取音乐片段还是其他需求&#xff0c;视频转音频都是一项非常实用的技能。为了让你轻松应对各种场合的需求&#xff0c;下文将为你详细…

【Linux】解锁系统编程奥秘,高效文件IO的实战技巧

文件 1. 知识铺垫2. C文件I/O2.1. C文件接口2.2 fopen()与重定向2.3. 当前路径2.4. stdin、stdout、stderr 3. 系统文件I/O3.1. 前言3.2. open3.2.1. flags</h3>3.2.2. mode</h3>3.2.3. 返回值fd 3.3. write</h2>3.4. read3.5. close</h2>3.6. lseek&l…

Android Manifest权限清单

Android权限部分可分为安装权限、运行时权限、特殊权限。 其中安装权限分普通权限和签名权限&#xff1a;普通权限安装后就有&#xff0c;无需重新授权&#xff1b;签名权限则需要系统签名才有的权限&#xff1b; 特殊权限则需要打开指定的系统页面进行授权&#xff0c;当然使用…

vue2中使用vue-office库预览pdf /docx/excel文件

vue2中使用vue-office库预览pdf /docx/excel文件 - 简书 vue引用vue-office实现docx、excel、pdf等文件预览_vue-office-excel-CSDN博客

OpenSSH从7.4升级到9.8的过程 亲测--图文详解

一、下载软件 下载openssh 下载地址&#xff1a; Downloads | Library 下载openssl Index of /pub/OpenBSD/OpenSSH/ zlib Home Site 安装的 openssl-3.3.1.tar.gz ,安装3.3.2有问题 安装有问题&#xff0c; 二、安装依赖 yum install -y perl-CPAN perl-ExtUtils-CB…

数据结构之算法复杂度

目录 前言 一、复杂度的概念 二、时间复杂度 三、大O的渐进表示法 四、空间复杂度 五、常见复杂度对比 总结 前言 本文主要讲述数据结构中的算法复杂度 一、复杂度的概念 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏…

并发容器(Map、List、Set)实战及其原理分析

1. JUC包下的并发容器 Java的集合容器框架中&#xff0c;主要有四大类别&#xff1a;List、Set、Queue、Map&#xff0c;大家熟知的这些集合类ArrayList、LinkedList、HashMap这些容器都是非线程安全的。 所以&#xff0c;Java先提供了同步容器供用户使用。同步容器可以简单地…