Python 装饰器使用详解

devtools/2024/11/14 15:42:47/

文章目录

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/devtools/114976.html

相关文章

谷粒商城のElasticsearch

文章目录 前言一、前置知识1、Elasticsearch 的结构2、倒排索引 (Inverted Index)2.1、 索引阶段2.2、查询阶段 二、环境准备1、安装Es2、安装Kibana3、安装 ik 分词器 三、项目整合1、引入依赖2、整合业务2.1、创建索引、文档、构建查询语句2.2、整合业务代码 后记 前言 本篇介…

两栏布局和三栏布局的实现方法

两栏布局 右侧不设置宽&#xff0c;实现一栏自适应。 1. float margin-left 左侧设置float&#xff0c;且设置宽度&#xff0c;右侧margin-left为左侧的宽度 <head><style>.left{width: 300px;height: 500px;background-color: palegreen;float: left;}.right…

Linux C高级day3

一、思维导图 二、练习 #!/bin/bash mkdir ~/dir mkdir ~/dir/dir1 mkdir ~/dir/dir2 cp -r * ~/dir/dir1/ cp -r *.sh ~/dir/dir2/ cd ~/dir/dir2/ tar -cvJf dir2.tar.xz dir2 mv dir2.tar.xz ~/dir/dir1/ cd ~/dir/dir1 tar -xvJf dir2.tar.xz #!/bin/bash head -5 /etc/gr…

Linux 进程3

进程地址空间 CPU读取数据都需要地址&#xff0c;在计算机中所有东西都是一种数据&#xff0c;包括我们的进程。 这是一个进程空间示意图&#xff0c;操作系统通过task_struct结构体链表来管理每一个进程&#xff0c;结构体里面有一个指针指向操作系统为进程开辟的一段空间&am…

Text-to-SQL技术升级 - 阿里云OpenSearch-SQL在BIRD榜单夺冠方法

Text-to-SQL技术升级 - 阿里云OpenSearch-SQL在BIRD榜单夺冠方法 Text-to-SQL 任务旨在将自然语言查询转换为结构化查询语言(SQL),从而使非专业用户能够便捷地访问和操作数据库。近期,阿里云的 OpenSearch 引擎凭借其一致性对齐技术,在当前极具影响力的 Text-to-SQL 任务…

Zookeeper 3.8.4 安装和参数解析

安装 zookeeper 之前必须先安装 JDK&#xff0c;有关Linux环境JDK可以参考我以前写的博文 1、关于Linux服务器配置java环境遇到的问题 2、Linux环境安装openJDK 3、Centos7.3云服务器上安装Nginx、MySQL、JDK、Tomcat环境 文章目录 1. zookeeper 安装2. 参数解析 1. zookeeper …

【开源免费】基于SpringBoot+Vue.JS网上购物商城(JAVA毕业设计)

本文项目编号 T 041 &#xff0c;文末自助获取源码 \color{red}{T041&#xff0c;文末自助获取源码} T041&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析5.4 用例设计 六、核…

【Obsidian】当笔记接入AI,Copilot插件推荐

当笔记接入AI&#xff0c;Copilot插件推荐 自己的知识库笔记如果增加AI功能会怎样&#xff1f;AI的回答完全基于你自己的知识库余料&#xff0c;是不是很有趣。在插件库中有Copilot插件这款插件&#xff0c;可以实现这个梦想。 一、什么是Copilot&#xff1f; 我们知道githu…