Python 装饰器进阶

news/2024/5/4 18:01:49/文章来源:https://blog.csdn.net/m0_56966142/article/details/127338309

一、装饰器的执行流程

上一篇文章介绍了装饰器的概念和基本使用,这篇我们来深入探索一下 python 的装饰器。

1.1 简单装饰器例子

我们先看一个例子,控制台会输出什么?

def decorator(func):def inner():print("inner start")func()print("inner end")return inner@decorator
def index():print("index")
index()>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
inner start
index
inner end

相信有基础的同学已经知道为什么是这样打印的内容了。我们来仔细分析一下整个执行流程:
index函数上面的@符号就是下面这一行代码的语法糖

index = decorator(index)

1.2 装饰器本质

装饰器的本质就是上面那一行代码。index = decorator(index)
接下来我们看这段代码:

def decorator(func):def inner():"""操作"""print("func 变量的地址: ", id(func))func()return innerdef index():print("输出:index")print("最初的 index 地址:", id(index))
inner = decorator(index)
print("返回的 inner 地址:", id(inner))
index = inner
print("inner赋值给index后的地址:", id(index))
index()
print("index 函数的 name 属性:"index.__name__)		>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
最初的 index 地址: 2336665790064
返回的 inner 地址: 2336665898576
inner赋值给index后的地址: 2336665898576
func 变量的地址:  2336665790064
输出:index
index 函数的 name 属性: inner
  1. 首先将index函数内存地址作为参数传递到decorator中,func变量作为接收参数,并返回了inner这个函数的内存地址。
  2. 此时 func = index(index的内存地址赋值给func变量)index = inner (inner的内存地址赋值给index变量)
  3. 通过打印发现func变量的地址和最初的index 函数地址是一样的
  4. 第15行代码,将inner函数的内存地址赋值给了index函数。此时的index函数就是inner函数。故在执行index函数就是执行的decorator函数内的inner函数。
  5. inner函数中,打印了func变量的内存地址,是最初的index函数地址并执行了func() ,此时就会将输出:index 内存打印出来
  6. 最后打印index函数的__name__属性发现,更新后的index函数就是inner函数。

使用语法糖装饰函数,发现最后输出的__name__ 属性也是inner

# 使用语法糖符号@ 给index函数添加装饰器decorator
def decorator(func):def inner():func()return inner@decorator
def index():print("输出:index")
index()
print("index 函数的 name 属性:", index.__name__)>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
输出:index
index 函数的 name 属性: inner

1.3 functools

1.2 小节中,给index函数添加装饰器后,此时index函数本质指向的装饰器内部的inner函数,如果我们想让index函数不要指向inner函数,而是指向原来的index函数。就需要使用functools包

from functools import wraps
def decorator(func):@wraps(func)def inner():func()return inner@decorator
def index():print("输出:index")
index()
print("index 函数的 name 属性:", index.__name__)>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
输出:index
index 函数的 name 属性: index

上面就是装饰器的本质了,若写得不清楚欢迎在评论区留言。

1.4 实现用户认证的简单案例

def wrapper(func):def inner(*args, **kwargs):if not kwargs['token']:print('您需要先登录')if not kwargs['token'] == 6666:		# 这里也可以写成数据库查询操作print('认证已失效,请重新登录')else:f = func(*args, **kwargs)return f 						# 可以实现多装饰器的传递return inner@wrapper
def order(token):print('这是用户订单')v = order(token=6666)

二、类装饰器

第一节都是讲述的函数装饰器,这一节我们来了解类装饰器
当前有一个需求:有时你只想打日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。
幸运的是,类也可以用来构建装饰器。那我们现在以一个类而不是一个函数的方式。

import settings
from functools import wraps
from datetime import datetime
class LogHandler:# settings.LOG_FILE = "log.txt" ,可以把配置信息抽离到配置文件中# settings.EMAIL_NOTIFY = Falsedef __init__(self, log_file=settings.LOG_FILE, send_email_status=settings.EMAIL_NOTIFY):  # 接收传递参数self.log_file = log_fileself.send_email_status = send_email_statusdef __call__(self, func):@wraps(func)def decorator(*args, **kwargs):current_time = datetime.now()with open(self.log_file, 'a') as fp:    # 写入数据fp.write(f"[{current_time}]  {func.__name__} is write log \n")### 这里可以对用户登录进行用户名认证,通过才能发送邮件 ##if self.send_email_status:             # 需要发送邮件self.send_email()return func(*args, **kwargs)return decoratordef send_email(self):print("send email")@LogHandler(send_email_status=True)  # 若不传递参数,则使用默认参数
def login():print("登录")login()

查看log文件内容
在这里插入图片描述

在LogHandler类中,我们定义了__init__方法__call__方法,

  • __init__会在类进行初始化时被调用 ,log_handler = LogHandler() 调用的init方法
  • __call__ 可以使实例能够像函数一样被调用,同时不影响实例本身的生命周期(__call__()不影响一个实例的构造和析构)。但是__call__()可以用来改变实例的内部成员的值。 log_handler(func) 调用的call方法

所以上述代码可以拆解为:

log_handler = LogHandler(params1, params2)   # 执行init
login = log_handler(login)  	             # 执行call
login()

三、多层装饰器

当我们给一个函数套上多层装饰器时,它们的执行流程时怎样的呢?下面我们来看一段代码

def decorator1(func):def inner1(*args,**kwargs):print("decorator1")print(func)    		# func => inner2 函数内存地址 (注释不明白看底下的解释)func()return inner1def decorator2(func):def inner2(*args,**kwargs):print("decorator2")print(func)			# func => foo 函数内存地址 (注释不明白看底下的解释)func()return inner2@decorator1   
@decorator2  
def foo():print("foo")
foo()>>>>>>>>>>>>>> 执行结果 >>>>>>>>>>>>>
decorator1
<function decorator2.<locals>.inner2 at 0x000002200C7264C0>
decorator2
<function foo at 0x000002200CDAB820>
foo
  1. 开始看结果是不是有点懵,我们仔细发现第一行输出的decorator1,可以发现最先执行的是inner1这个函数。第二行输出的是inner2函数地址,表示我们的decorator1 中的func指向的是decorator2 中的inner2函数。
  2. 第三行输出decorator2,表示执行了decorator2 中的inner2函数,第四行输出了foo函数,表示decorator2 中的func指向的foo函数。
  3. 最后输出了foo函数本身的内容
  4. 执行流程图如下:
decorator1() --> inner1() --> decorator2() --> inner2() --> foo()
  1. **得出结论:**在多层装饰器中,执行流程是从上到下执行的。
  • foo函数的具体初始过程是怎样的呢?

foo函数的初始过程是从下到上的,执行过程是从上到下的

# 1.先执行decorator2函数,并见foo函数地址传入
foo = decorator2(foo) 
# 赋值后:foo = inner2 函数的内存地址, func 指向开始的foo内存地址# 2.再执行decorator1函数,并将foo函数传入(foo函数本质是inner2 函数内存地址,即decorator1(inner2))
foo = decorator1(foo) 
#  2.1 此时decorator1函数中 func 指向的是inner2 函数的内存地址
#  2.2 赋值后:foo = inner1 函数的内存地址# 3.执行foo函数
foo()  
# 3.1 等同于执行 decorator1函数中 inner1() 函数

以上就是多层装饰器的指向流程了。

四、带参数的装饰器

现在我们有一个需求:接口要实现一个权限认证,如果用户有这个权限,则允许访问这个api,否则不返回值。
类似结构如下,

@auth_permission(["VIEW","EDIT"])
def get_book_list(request):"""获取book列表"""
  • 实现
from functools import wraps
def auth_permission(permission_list):def decorator(func):@wraps(func)def inner(*args, **kwargs):user_id = kwargs.get("user_id",None)if user_id:# 获取该用户数据库中的所有权限,这里使用了django的 ORM,也可以使用sql语句# permission_codes = models.UserPermission.objects.filter(user_id=user_id).first()# 为了测试,手动定义用户idif user_id == 1: 					# id=1的用户只有VIEW权限,permission_codes = ["VIEW"]else:								# 其他用户为"DELETE","EDIT" 权限permission_codes = ["DELETE","EDIT"]# any() 函数表示只要有一个为True,就返回Trueresult = any(permission in permission_codes for permission in permission_list) # 表示设置的权限有一个在用户的所有权限中就返回Trueif result:return func()else:return "当前用户权限不够"else:return "请传递user_id"return innerreturn decorator@auth_permission(["DELETE","EDIT"])
def get_book():"""获取book列表"""data = {"id": 1,"name": "骆驼祥子"}print(data)
  • 测试
    在这里插入图片描述

  • 添加了参数后的初始化过程是怎样的呢?

可以发现在上面的代码中,我们嵌套了2层闭包。这2层闭包具体指代的是什么呢?

# 1. 先执行函数auth_permission,并见参数传入
decorator = auth_permission(["xx"])# 2.此时@auth_permission(["xx"]) = @decorator
@decorator
def get_book():"""获取book列表"""data = {"id": 1,"name": "骆驼祥子"}print(data)# 3. 将函数get_book 传入decorator ,
# 即 get_book = decorator(get_book) ,这一步发现是不是就是前面的函数装饰器,func 指代 get_book函数地址
get_book(user_id=1)  # *args 接收一个非键值对的参数,并以元组的形式返回, **kwargs 接收键值对的参数,并以字典返回

五、Django require_http_methods装饰器解读

我们来分析Django中的 require_http_methods 这个装饰器。
require_http_methods 这个装饰器可以指定前端的请求方式是POST请求还是GET请求

from django.views.decorators.http import require_http_methods
@require_http_methods(['POST'])
def get_book_list(request):"""视图函数操作"""return JsonResponse({'code': 0, 'results': "successs"})

我们点进去查看源码:

def require_http_methods(request_method_list):"""Decorator to make a view only accept particular request methods.  Usage::@require_http_methods(["GET", "POST"])def my_view(request):# I can assume now that only GET or POST requests make it this far# ...Note that request methods should be in uppercase."""def decorator(func):@wraps(func)def inner(request, *args, **kwargs):if request.method not in request_method_list:response = HttpResponseNotAllowed(request_method_list)log_response('Method Not Allowed (%s): %s', request.method, request.path,response=response,request=request,)return responsereturn func(request, *args, **kwargs)return innerreturn decorator
  1. 第一步是获取装饰器中的指定的请求方法列表,变量request_method_list 来接收装饰器中的参数。
  2. 第二步get_book_list函数就是inner函数,通过request获取到当前的请求方式,如果当前请求方式不在定义的请求列表中,则返回请求方法不允许。在请求列表中则执行get_book_list函数

是不是发现装饰器还是很简单的。好了,以上就是装饰器进阶的全部内容,本人水平有限,若文章存在错误欢迎在评论区指出,谢谢~

参考地址:https://www.runoob.com/w3cnote/python-func-decorators.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_24009.aspx

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

相关文章

数据结构与算法之Python实现——线性表(一)

&#x1f433; 前言 数据结构与算法的一刷是在前几个月的时候用C语言区实现的&#xff0c;那时候也刚开始接触C语言&#xff0c;只知道个C语言的大概&#xff0c;然后却不怎么会应用。 之后在网上买了一本数据结构的书后就开始用C语言去学习。在用C语言去学习的过程中&#x…

pytorch 神经网络特征可视化

可参考博客 Pytorch可视化模型任意中间层的类激活热力图(Grad-CAM)_潜行隐耀的博客-CSDN博客_pytorch热力图 Pytorch输出网络中间层特征可视化_Joker-Tong的博客-CSDN博客_输出网络中间特征图 GitHub - utkuozbulak/pytorch-cnn-visualizations: Pytorch implementation of …

浅谈IT系统性能优化

一个刚上线的IT系统,往往负载压力不大,所以不会存在什么性能问题。这时,人们大多只关心系统的功能性和用户体验。但是,随着时间推移,用户量和数据量都比刚上线的时候要多很多,高并发和大数据场景下,系统遇到性能瓶颈,持续不能改善最终导致系统崩溃。这对于做C端的开发人…

<Python的变量创建与使用>——《Python》

目录 1.常量和表达式 2.变量和类型 3.变量的语法 后记&#xff1a;●由于作者水平有限&#xff0c;文章难免存在谬误之处&#xff0c;敬请读者斧正&#xff0c;俚语成篇&#xff0c;恳望指教&#xff01; …

python语言思想

python语言基础与应用 超级计算器 python语言解释器 为啥选用PYCHARM create new project: NANE 选择解释器 open &#xff0c;选择打开文件或者加入project 注意对齐与缩进 注意字母大小写、空格 注意左右括号配对 错误是常见的&#xff0c;跟BUG和缺陷斗争得到过程 观察代…

08 字符串连接符 “+“ 导致的 check cast 的省略

前言 // 年轻时候&#xff0c;到了冬天&#xff0c;家人让你穿秋裤&#xff0c;你不仅不穿秋裤&#xff0c;还露着脚脖子&#xff0c;如果有人劝你&#xff0c;你会嫌他唠叨。而等你岁数大一点&#xff0c;天气一冷&#xff0c;身体受不了&#xff0c;就自觉把秋裤穿上了。 呵…

图论二分图问题讲解-染色法和匈牙利算法

二分图 概述&#xff1a; 二分图又称作二部图&#xff0c;是图论中的一种特殊模型。 设G(V,E)是一个无向图&#xff0c;如果顶点V可分割为两个互不相交的子集(A,B)&#xff0c;并且图中的每条边&#xff08;i&#xff0c;j&#xff09;所关联的两个顶点i和j分别属于这两个不同的…

使用Python将微信和支付宝账单导入随手记

简介 本文介绍如何使用Python将微信和支付宝账单转换为可以导入随手记的文件&#xff0c;实现微信和支付宝账单的批量导入。 需求&#xff1a; 1、需要将支付宝和微信上的支出账单自动或半自动地导入到随手记中 已知信息&#xff1a; 1、支付宝和微信的app端都可以导出csv…

引导过程与服务控制

目录: 1、引导过程总览 2、备份与恢复第一块硬盘前512字节 3、修复GRUB引导故障 4、忘记密码 5、开关系统服务控制Linux操作系统引导过程引导过程总览: 开机自检→MBR引导→GRUB菜单→加载内核→init进程初始化 1、bios 检查硬件设置grub功能和组成 bootloader:引导加载器,…

npm install ,npm ERR code 401 Incorrect or missing password 错误原因与.npmrc 配置文件的使用

前言&#xff1a;前端去维护项目时&#xff0c;通过 git clone 下来以后&#xff0c;经常是直接 npm install 去安装项目需要的 node_modules &#xff0c;但是往往很多项目不是我们自己写的&#xff0c;或者从 GitHub 上面 clone 的开源项目&#xff0c;这个时候出现问题就很难…

【ASM】字节码操作 转换已有的类 ClassReader 删除方法 添加方法

文章目录 1.概述2.案例2.1 删除方法2.2 添加方法2.3小总结3.总结1.概述 上一篇文章:【ASM】字节码操作 转换已有的类 ClassReader 修改字段信息 删除字段 增加字段 在上一篇文章中我们学到了如何添加字段与删除字段。 本章节我们来尝试修改方法和删除方法。 2.案例 2.1 删…

搜索查找类

查找搜索类\color{blue}{\huge{查找搜索类}}查找搜索类 find find指令从指定目录向下递归地便利各个子目录&#xff0c;如果在/root目录下进行寻找&#xff0c;根据文件目录的树状结构&#xff0c;就是进行全盘查找&#xff0c;非常浪费时间&#xff0c;所以使用find 进行寻找…

MATLAB | 绘图复刻(二) | 折线图+误差棒+柱状图+散点抖动+灰色背景+图片叠加

看到gzh R语言ggplot2科研绘图发布了一篇绘图复刻类文章&#xff0c;复刻了&#xff1a; Nature(IF49.962)文章(Gut microbiota modulates weight gain in mice after discontinued smoke exposure)其中的Figure.1b&#xff0c;绘制效果十分惊艳&#xff0c;手痒就想拿MATLAB也…

RocketMQ 消费者Rebalance算法 解析——图解、源码级解析

&#x1f34a; Java学习&#xff1a;Java从入门到精通总结 &#x1f34a; 深入浅出RocketMQ设计思想&#xff1a;深入浅出RocketMQ设计思想 &#x1f34a; 绝对不一样的职场干货&#xff1a;大厂最佳实践经验指南 &#x1f4c6; 最近更新&#xff1a;2022年10月15日 &#…

(附源码)计算机毕业设计大学生网上书店

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

(附源码)计算机毕业设计电脑外设销售系统小程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

操作系统基本功能(操作系统)

目录 一、处理机管理 二、存储器管理 三、设备管理 四、文件管理 五、作业管理 一、处理机管理 中央处理机&#xff08;CPU&#xff09;是计算机系统中一个举足轻重的资源。用户程序进入内存后&#xff0c;只有获得CPU&#xff0c;才能真正得以运行。 为了提高CPU的利用率…

前端都应该了解的 NodeJs 知识及原理浅析

node.js 初探 Node.js 是一个 JS 的服务端运行环境&#xff0c;简单的来说&#xff0c;它是在 JS 语言规范的基础上&#xff0c;封装了一些服务端的运行时对象&#xff0c;让我们能够简单实现非常多的业务功能。 如果我们只使用 JS 的话&#xff0c;实际上只是能进行一些简单…

docker mysql8使用SSL及使用openssl生成自定义证书

《docker安装MySQL8》 修改my.cnf vi /docker_data/mysql/conf/my.cnf[client] default-character-setutf8mb4 [mysql] default-character-setutf8mb4 [mysqld] character-set-serverutf8mb4 default_authentication_pluginmysql_native_password #增加ssl ssl保存&#xff0…

【让你从0到1学会c语言】文件操作

作者&#xff1a;喜欢猫咪的的程序员 专栏&#xff1a;《C语言》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 什么是文件&#xff1a; 我们为什么要使用文件呢&#xff1f; 文件分类&#x…