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.mfbz.cn/a/881297.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis学习以及SpringBoot集成使用Redis

目录 一、Redis概述 二、Linux下使用Docker安装Redis 三、SpringBoot集成使用Redis 3.1 添加redis依赖 3.2 配置连接redis 3.3 实现序列化 3.4 注入RedisTemplate 3.5 测试 四、Redis数据结构 一、Redis概述 什么是redis&#xff1f; redis 是一个高性能的&#xf…

vue项目加载cdn失败解决方法

注释index.html文件中 找到vue.config.js文件注释、

【Python语言初识(二)】

一、分支结构 1.1、if语句 在Python中&#xff0c;要构造分支结构可以使用if、elif和else关键字。所谓关键字就是有特殊含义的单词&#xff0c;像if和else就是专门用于构造分支结构的关键字&#xff0c;很显然你不能够使用它作为变量名&#xff08;事实上&#xff0c;用作其他…

0基础带你入门Linux之使用

1.Ubuntu软件管理 回顾一下&#xff0c;我们之前使用su root切换到root模式&#xff0c;使用who 发现为什么显示的还是bd用户呢&#xff1f;为什么呢&#xff1f; 这个who是主要来查看的是我们登录的时候是以什么用户登录的 所以即使我们使用who进行查看的时候显示的还是bd用…

【JavaWeb】利用IDEA2024+tomcat10配置web6.0版本搭建JavaWeb开发项目

之前写过一篇文章&#xff1a;《【JavaWeb】利用IntelliJ IDEA 2024.1.4 Tomcat10 搭建Java Web项目开发环境&#xff08;图文超详细&#xff09;》详细讲解了如何搭建JavaWeb项目的开发环境&#xff0c;里面默认使用的Web版本是4.0版本的。但在某些时候tomcat10可能无法运行we…

提升效率的AI工具集 - 轻松实现自动化

在这个快节奏、高效率的社会中&#xff0c;我们每个人都渴望能够找到提升工作效率的捷径。幸运的是&#xff0c;随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;越来越多的AI工具涌现出来&#xff0c;为我们提供了强大的支持。这些工具不仅能够帮助我们提高…

计算机毕业设计 美发管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

尚品汇-秒杀成功下单接口、秒杀结束定时任务-清空缓存数据(五十四)

目录&#xff1a; &#xff08;1&#xff09;下单页面 &#xff08;2&#xff09;service-activity-client添加接口 &#xff08;3&#xff09;web-all 编写去下单控制器 &#xff08;4&#xff09;service-order模块提供秒杀下单接口 &#xff08;5&#xff09;service-or…

安全基础学习-AES128加密算法

前言 AES&#xff08;Advanced Encryption Standard&#xff09;是对称加密算法的一个标准&#xff0c;主要用于保护电子数据的安全。AES 支持128、192、和256位密钥长度&#xff0c;其中AES-128是最常用的一种&#xff0c;它使用128位&#xff08;16字节&#xff09;的密钥进…

推荐系统-电商直播 多目标排序算法探秘

前言&#xff1a; 电商直播已经成为电商平台流量的主要入口&#xff0c;今天我们一起探讨推荐算法在直播中所面临的核心问题和解决方案。以下内容参考阿里1688的技术方案整理完成。 一、核心问题介绍 在电商网站中&#xff0c;用户的主要行为是在商品上的行为&#xff0c;直播…

机器学习笔记(一)初识机器学习

1.定义 机器学习是一门多学科交叉专业&#xff0c;涵盖概率论知识&#xff0c;统计学知识&#xff0c;近似理论知识和复杂算法知识&#xff0c;使用计算机作为工具并致力于真实实时的模拟人类学习方式&#xff0c;并将现有内容进行知识结构划分来有效提高学习效率。 机器学习有…

JUC学习笔记(三)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 八、共享模型之工具--JUC8.1 AQS 原理1. 概述2 实现不可重入锁自定义同步器自定义锁 3.心得起源目标设计1) state 设计2&#xff09;阻塞恢复设计3&#xff09;队列…

Stable Diffusion不同部件拆分详解

看到很多文章对Stable Diffusion各种原理、详解等&#xff0c;但是么有看到有文章细拆里面各个子模块在做啥&#xff0c;怎么做的&#xff0c;所以就会遇到整体原理理解很透传&#xff0c;问到细节就卡住&#xff0c;这段时间细看了一下文章&#xff0c;对各个部分做一个拆解详…

网络基础,协议,OSI分层,TCP/IP模型

网络的产生是数据交流的必然趋势&#xff0c;计算机之间的独立的个体&#xff0c;想要进行数据交互&#xff0c;一开始是使用磁盘进行数据拷贝&#xff0c;可是这样的数据拷贝效率很低&#xff0c;于是网络交互便出现了&#xff1b; 1.网络是什么 网络&#xff0c;顾名思义是…

【SQL Server】清除日志文件ERRORLOG、tempdb.mdf

数据库再使用一段时间后&#xff0c;日志文件会增大&#xff0c;特别是在磁盘容量不足的情况下&#xff0c;更是需要缩减&#xff0c;以下为缩减方法&#xff1a; 如果可以停止 SQL Server 服务&#xff0c;那么可以采取更直接的方式来缩减 ERRORLOG 和 tempdb.mdf 文件的大小…

【IoTDB 线上小课 07】多类写入接口,快速易懂的“说明书”!

【IoTDB 视频小课】稳定更新中&#xff01;第七期来啦~ 关于 IoTDB&#xff0c;关于物联网&#xff0c;关于时序数据库&#xff0c;关于开源... 一个问题重点&#xff0c;3-5 分钟&#xff0c;我们讲给你听&#xff1a; 一条视频了解写入接口 了解我们的友友们&#xff0c;应该…

centos 安装VNC,实现远程连接

centos 安装VNC&#xff0c;实现远程连接 VNC(Virtual Network Computing)是一种远程控制软件&#xff0c;可以实现通过网络远程连接计算机的图形界面。 服务器安装VNC服务 yum install -y tigervnc-server*启动VNC服务&#xff0c;过程中需要输入连接密码 vncserver :1查看…

【chromedriver编译-绕过selenium机器人检测】

有小伙伴说使用selenium没能绕过机器人检测&#xff0c;盘他。 selenium机器人检测有2种&#xff0c;一是cdp检测&#xff0c;二是webdriver特征检测。cdp检测前面的博客已写过&#xff0c;这里就提下webdriver特征检测。一、selenium简介 Selenium 是一个强大的工具&#xff…

硬件工程师笔试面试——开关

目录 11、开关 11.1 基础 开关原理图 开关实物图 11.1.1 概念 11.1.2 常见的开关类型及其应用 11.2 相关问题 11.2.1 开关的工作原理是什么? 11.2.2 在设计一个电子系统时,如何选择最适合的开关类型? 11.2.3 不同类型的开关在实际应用中有哪些优势和局限性? 11.…

css设置overflow:hiden行内元素会发生偏移的现象

父级元素包含几个行内元素 <div id"box"><p><span>按钮</span><span>测试文字文字文字测试文字文字文字</span><span>看这里</span></p></div>#box p{width: 800px;font-size: 30px;}#box p span{disp…