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

day17:一文弄懂“无参装饰器”、“有参装饰器”和“叠加装饰器”

目录

  • 一、无参装饰器
    • 1. 什么是装饰器
    • 2. 为何要用装饰器
    • 3. 如何用
      • 解决方案一:失败,优化见↓↓↓
      • 解决方案二:失败,优化见↓↓↓
      • 解决方案三:失败,优化见↓↓↓
        • 方案三的优化一:将index的参数写活了
        • 方案三的优化二:在优化一的基础上把被装饰对象写活了
        • 方案三的优化三:将wrapper做的跟被装饰对象一模一样,以假乱真
    • 4. 语法糖:让你开心的语法
    • 5. 总结无参装饰器模板
    • 6. 叠加多个装饰器,加载顺序与运行顺序
    • 7. 案例讲解:@语法糖底层逻辑
    • 8.wraps装饰器介绍
    • 9. 思考:为什么会有两次时间计算?
  • 二、有参装饰器
    • 1. 知识储备
    • 2.不使用有参装饰器的山炮玩法 <案例1>
    • 3.使用有参装饰器的山炮玩法 <案例2>
    • 3.使用有参装饰器的最好玩法 <案例3>
    • 4.有参装饰器模板
  • 三、叠加装饰器
    • ↑↑↑通过案例引出结论,结论见上方↑↑↑

一、无参装饰器

1. 什么是装饰器

装饰:指的是为其他事物添加额外的东西点缀
器:指的是工具,可以定义成成函数

合到一起的解释:
装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能

2. 为何要用装饰器

开放封闭原则
开放:指的是对拓展功能是开放的(新增功能)
封闭:指的是对修改源代码是封闭的

<装饰器>:就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能

3. 如何用

需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能

'''案例:思考如何为该函数添加统计时间的功能'''def index(x,y):print('index函数打印:%s %s'%(x, y))index(11, 22)
index(11, y=22)
index(x=11, y=22)
index(y=22, x=22)

解决方案一:失败,优化见↓↓↓

问题:没有修改被装饰对象的调用方式,但是修改了其源代码

import timedef index(x,y):start_time = time.time()time.sleep(1)print('index函数打印:%s %s'%(x, y))stop_time = time.time()print('函数index运行时间为:{}'.format(stop_time-start_time))index(111,222)

解决方案二:失败,优化见↓↓↓

问题:没有修改被装饰对象的调用方式,也没有修改了其源代码,并且加上了新功能
但是代码冗余,index被调用的位置有很多,每个位置都上下夹击的写

import timedef index(x,y):print('index函数打印:%s %s'%(x, y))start_time = time.time()
index(11,22)
stop_time = time.time()
print(f'函数index运行时间为{stop_time-start_time}')start_time = time.time()
index(333,444)
stop_time = time.time()
print(f'函数index运行时间为{stop_time-start_time}')

解决方案三:失败,优化见↓↓↓

问题:解决了方案二代码冗余问题,但带来一个新问题即函数的调用方式改变了

import timedef index(x,y):time.sleep(1)print('index函数打印:%s %s'%(x, y))def wrapper():start_time = time.time()index(111, 222)  # 这里参数写死了stop_time = time.time()print(f'函数index运行时间为{stop_time-start_time}')wrapper()
方案三的优化一:将index的参数写活了
import timedef index(x,y):time.sleep(1)print('index函数打印:%s %s'%(x, y))def wrapper(*args,**kwargs): # 接收参数,整合start_time = time.time()index(*args,**kwargs)  # 打散stop_time = time.time()print(f'函数index运行时间为{stop_time-start_time}')wrapper(111,222)
wrapper(333,444)

在这里插入图片描述

方案三的优化二:在优化一的基础上把被装饰对象写活了

原来wrapper里面函数index名写死了,只能装饰index,其实可将函数名当做参数传给wrapper

import timedef index(x,y):time.sleep(1)print('index函数打印:%s %s'%(x, y))def outter(func):def wrapper(*args,**kwargs): # 接收参数,整合start_time = time.time()func(*args,**kwargs)  # 打散stop_time = time.time()print(f'函数index运行时间为{stop_time-start_time}')return wrapper  # 返回wrapper函数的内存地址# fff = outter(index) # 1、outter()返回的是wrapper函数内存地址,加()表示调用函数wrapper# 2、其中fff可以自定义,也可定义成index;
# fff(111,222)
index = outter(index) 
index(111,222)

在这里插入图片描述

方案三的优化三:将wrapper做的跟被装饰对象一模一样,以假乱真

①如果需要获取装饰的函数中有返回值,怎么办?使用res接收。

import timedef index(x,y):time.sleep(1)print('index函数打印:%s %s'%(x, y))def home(name):time.sleep(1)print('welcome %s to home page' %name)return 123def outter(func):def wrapper(*args,**kwargs): # 接收参数,整合start_time = time.time()res = func(*args,**kwargs)  # 先打散,再执行func函数,最后将返回值给resstop_time = time.time()print(f'函数index运行时间为{stop_time-start_time}')return resreturn wrapper home = outter(home)  # 偷梁换柱:home这个名字指向的wrapper函数的内存地址
res = home('linhui')
print('res==》', res)

在这里插入图片描述

4. 语法糖:让你开心的语法

【注】:需要在定义函数前先定义好装饰器,如果调用的函数在装饰器前面,运行程序时会提示名为’xxx’的装饰器未定义

import time# 装饰器
def timmer(func):def wrapper(*args,**kwargs):start=time.time()res=func(*args,**kwargs)stop=time.time()print(stop - start)return resreturn wrapper# # 在被装饰对象正上方的单独一行写@装饰器名字
@timmer  # index=timmer(index)
def index(x,y,z):time.sleep(1)print('index %s %s %s' %(x,y,z))@timmer # home=timmer(ome)
def home(name):time.sleep(1)print('welcome %s to home page' %name)index(x=1,y=2,z=3)
home('大飞飞')

5. 总结无参装饰器模板

def outter(func):def wrapper(*args,**kwargs):# 1、调用原函数# 2、为其增加新功能res=func(*args,**kwargs)return resreturn wrapper

6. 叠加多个装饰器,加载顺序与运行顺序

@deco1  # index=deco1(deco2.wrapper的内存地址)
@deco2  # deco2.wrapper的内存地址=deco2(deco3.wrapper的内存地址)
@deco3  # deco3.wrapper的内存地址=deco3(index)
def index():pass

7. 案例讲解:@语法糖底层逻辑

注:为有参装饰器做知识储备

【案例1说明】:@print==>等价于index = print(index),即print(index)打印的是index的函数内存地址

'''案例1:举例@print作为装饰器'''@print    # @print==>等价于index = print(index)
def index(x,y):print(1111)pass

输出:
<function index at 0x7f2d6e3f01e0>

【案例2说明】:@print==>index = print(index),先打印index内存地址,然后将print(index)函数运行后的返回值赋给了index,这里的print()装饰器本身没有返回值,所以print(index)=None

'''案例2:举例@print作为装饰器'''@print
def index(x,y):print(1111)passprint(index)

输出:
function index at 0x7fa2763151e0>
None

【案例3说明】:
这里的@print(‘hello’) 等价于:
第一步:先运行print(hello),输出hello
第二步:print(hello)的返回结果为None
第三步:@print(‘hello’) 等价于 @None
第四步:@None 等价于 index=None(index) ,故最终执行报错

'''案例3:举例@print作为装饰器'''
@print('hello')
def index(x,y):print(1111)pass

hello
Traceback (most recent call last):
File “/home/xionglinhui/my_python_project/temp.py”, line 15, in
@print(‘hello’)
TypeError: ‘NoneType’ object is not callable

8.wraps装饰器介绍

举例说明:列举不调用装饰器和调用装饰器时函数index的属性区别,通过案例1和案例2引出进一步被装饰对象在被装饰器调用前和调用后的区别,从而优化下装饰器代码,最终目的是将wrapper函数做的和原函数一模一样。

''' 案例1 没有调用装饰器,查看原函数的函数名和功能说明 '''def outter(func):def wrapper(*args, **kwargs):res = func(*args, **kwargs)return resreturn wrapperdef index(x, y):'''我是index,我的功能是打印参数'''print(f'打印index的x,y ==> {x} {y}')return '我是index返回值'# 注释掉@outter装饰器,查看index函数地址和函数功能说明
index = outter(index)
print(index)  # 输出:<function outter.<locals>.wrapper at 0x00000263568EF910>
# 翻译即是函数outter的局部函数wrapper的内存地址
index(666, 999)  # 打印index的x,y ==> 666 999print(index.__name__)  # 获取函数index的名字,即index
print(index.__doc__)  # 获取函数index的功能说明,即'''xxx'''里面的内容,即“我是index,我的功能是打印参数”
''' 案例2 调用装饰器,查看原函数的函数名和功能说明 '''
def outter(func):def wrapper(*args, **kwargs):res = func(*args, **kwargs)return resreturn wrapper@outter
def index(x, y):'''我是index,我的功能是打印参数'''print(f'打印index的x,y ==> {x} {y}')return '我是index返回值'#调用装饰器@outter后,查看index函数地址和函数功能说明
print(index)  # <function outter.<locals>.wrapper at 0x0000022FC505F880>
print(index.__name__)   # wrapper
print(index.__doc__)   # None

分析

  1. index(或定义为xxx) = outter(index)相当于把wrapper函数的内存地址赋给index,这里的index可以是任意变量;
  2. 这里的index实际上是wrapper,当我们查看index.__name__函数名和index.__doc__功能说明时,实际上查看的时wrapper的功能说明,与我们的偷梁换柱目的还差一点。

解决思路:优化方案请见《案例3》

  1. index调用outter装饰器后,index.__name__值为wrapper,
    而index调用outter装饰器前,index.__name__值为index,
    所以在调用装饰器前,即 定义阶段 需要将原函数index.__name____的名字传给wrapper.__name__名字,即:见下图
wrapper.__name__ = func.__name__   #函数名
wrapper.__doc__ = func.__doc__     #函数功能说明,即注释
''' 案例3 将index函数的属性值赋给wrapper,即例如wrapper.__name__ = func.__name__'''def outter(func):def wrapper(*args, **kwargs):res = func(*args, **kwargs)return res'''我是wrapper函数内的'''#模板:#函数wrapper.__name__  = 被装饰对象(原函数).__name__#函数wrapper.__doc__  = 被装饰对象(原函数).__doc__wrapper.__name__ = func.__name__  ##代码优化部分wrapper.__doc__ = func.__doc__return wrapper@outter
def index(x, y):'''我是index,我的功能ABCDEFG是打印参数'''print(f'打印index的x,y ==> {x} {y}')return '我是index返回值'#优化一:将index函数的属性值赋给wrapper
print(index)  # <function outter.<locals>.wrapper at 0x0000022FC505F880>
print(index.__name__)   # index
print(index.__doc__)   # 我是index,我的功能ABCDEFG是打印参数

存在的问题和解决方法
1.问题:函数的属性有很多,见下图,如果将每个属性都传给wrapper,那么代码会过于冗余。
2.解决方法:解释器考虑到这一点,特定封装了wraps装饰器用于解决该问题,该函数封装在functools,调用方式为from functools import wraps,见**《案例4》**
在这里插入图片描述

''' 案例4 使用wraps内置装饰器,优化代码'''from functools import wrapsdef outter(func):@wraps(func)def wrapper(*args, **kwargs):'''我是wrapper函数内的'''res = func(*args, **kwargs)return resreturn wrapper@outter
def index(x, y):'''我是index,我的功能ABCDEFG是打印参数'''print(f'打印index的x,y ==> {x} {y}')return '我是index返回值'#优化二:使用wraps内置装饰器,优化代码
print(index)  # <function index at 0x000001F86E5C65F0>
print(index.__name__)   # index
print(index.__doc__)   # 我是index,我的功能ABCDEFG是打印参数

9. 思考:为什么会有两次时间计算?

1.要求:详细说明代码执行流程
==========================
2.分析过程: print(outter(index)(11,22))
①outter(index) ==> 注意哈,这里的index就是被装饰对象index函数的内存地址 ;outter(index) ==>等价于index = wrapper函数的内存地址
②wrapper(11,22) ==>执行wrapper函数,当遇到func(当前的func就是被装饰对象index)时代码会跳转到index()函数,识别到了装饰器@outter,此时会优先执行@outter,@outter等价于outter(被装饰对象index),先去执行一遍wrapper函数且执行index函数内部代码
③跳过装饰器,继续执行步骤2中的wrapper(11,22)

'''案例:存在装饰器却不直接使用装饰器,分析输出结果!!'''import timedef outter(func):def wrapper(*args,**kwargs):start = time.time()res = func(*args,**kwargs)end = time.time()print(f'代码运行时间:{end-start}')return resreturn wrapper@outter  # index=outter(index)=wrapper函数内存地址
def index(x, y):time.sleep(1)print(f'打印index的输出:{x} {y}')return 12345678print(outter(index)(11,22))

在这里插入图片描述

二、有参装饰器

1. 知识储备

① 由于语法糖@的限制,outter函数只能有一个参数,并且该参数只用来接收被装饰对象的内存地址

② 案例需求:实现一个用来为被装饰对象添加认证功能的装饰器。认证成功后,如果文件类型为file,则执行func;如果文件类型为mysql,则打印’基于mysql的验证’;;如果文件类型为ldap,则打印’基于ldap的验证’。

def outter(func):# func = 函数的内存地址def wrapper(*args,**kwargs):res=func(*args,**kwargs)return resreturn wrapper# @outter # index=outter(index) # index=>wrapper
@outter # outter(index)
def index(x,y):print(x,y)

偷梁换柱之后,我们的正真目的是:↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
1.index的参数什么样子,wrapper的参数就应该什么样子
2.index的返回值什么样子,wrapper的返回值就应该什么样子
3.index的属性什么样子,wrapper的属性就应该什么样子==》from functools import wraps

2.不使用有参装饰器的山炮玩法 <案例1>

'''案例1:纯函数的调用'''
def auth(func,db_type):def wrapper(*args, **kwargs):name=input('your name>>>: ').strip()pwd=input('your password>>>: ').strip()if db_type == 'file':print('基于文件的验证')if name == 'dafeifei' and pwd == '123':res = func(*args, **kwargs)return reselse:print('user or password error')elif db_type == 'mysql':print('基于mysql的验证')res = func(*args, **kwargs)return reselif db_type == 'ldap':print('基于ldap的验证')else:print('不支持该db_type')return wrapper# @auth  # 账号密码的来源是文件
def index(x,y):print('index->>%s:%s' %(x,y))# @auth # 账号密码的来源是数据库
def home(name):print('home->>%s' %name)# @auth # 账号密码的来源是ldap
def transfer():print('transfer')# index=auth(index,'file')
# index(11,22)
home=auth(home,'mysql')
home('dafeifei')
# transfer=auth(transfer,'ldap')
# transfer()

3.使用有参装饰器的山炮玩法 <案例2>

'''案例2:deco=auth(db_type='file'),'''
def auth(db_type):def deco(func):def wrapper(*args, **kwargs):name=input('your name>>>: ').strip()pwd=input('your password>>>: ').strip()if db_type == 'file':print('基于文件的验证')if name == 'egon' and pwd == '123':res = func(*args, **kwargs)return reselse:print('user or password error')elif db_type == 'mysql':print('基于mysql的验证')elif db_type == 'ldap':print('基于ldap的验证')else:print('不支持该db_type')return wrapperreturn decodeco=auth(db_type='file')
@deco # 账号密码的来源是文件
def index(x,y):print('index->>%s:%s' %(x,y))deco=auth(db_type='mysql')
@deco # 账号密码的来源是数据库
def home(name):print('home->>%s' %name)deco=auth(db_type='ldap')
@deco # 账号密码的来源是ldap
def transfer():print('transfer')index(1,2)
# home('egon')
# transfer()

3.使用有参装饰器的最好玩法 <案例3>

【解释】:@auth(db_type=‘file’) ,见到()表示函数调用,先忽略@
第一步:auth(db_type=‘file’) ==>调用auth()返回的是deco函数的内存地址,即 index = deco的内存地址 ,得到了@deco,包含了对外部作用域名字db_type的引用,@deco的语法意义与无参装饰器一样;
第二步:@deco ==> 等价于index = deco(index),调用deco(index)返回的是wrapper函数的内存地址,即index = wrapper函数地址

'''案例3:语法糖'''def auth(db_type):def deco(func):def wrapper(*args, **kwargs):name = input('your name>>>: ').strip()pwd = input('your password>>>: ').strip()if db_type == 'file':print('基于文件的验证')if name == 'egon' and pwd == '123':res = func(*args, **kwargs)  # index(1,2)return reselse:print('user or password error')elif db_type == 'mysql':print('基于mysql的验证')elif db_type == 'ldap':print('基于ldap的验证')else:print('不支持该db_type')return wrapperreturn deco@auth(db_type='file')  # @deco # index=deco(index) # index=wrapper
def index(x, y):print('index->>%s:%s' % (x, y))@auth(db_type='mysql')  # @deco # home=deco(home) # home=wrapper
def home(name):print('home->>%s' % name)@auth(db_type='ldap')  # 账号密码的来源是ldap
def transfer():print('transfer')index(1, 2)
# home('egon')
# transfer()

4.有参装饰器模板

'''******有参装饰器模板******'''def 有参装饰器(x,y,z):def outter(func):def wrapper(*args, **kwargs):res = func(*args, **kwargs)return resreturn wrapperreturn outter@有参装饰器(1,y=2,z=3)
def 被装饰对象():pass

三、叠加装饰器

记住结论!记住结论!记住结论!
【叠加装饰器的加载顺序】:自下而上(了解)
【叠加装饰器的执行顺序】:自上而下(重点),最下面那个装饰器调用的才是原函数

↑↑↑通过案例引出结论,结论见上方↑↑↑

'''案例:通过案例详细了解下叠加装饰器的加载和执行逻辑,最后我们只需记住结论即可'''def deco1(func1): #func1 = wrapper2的内存地址def wrapper1(*args, **kwargs):print('正在运行===>deco1.wrapper1')res1 = func1(*args, **kwargs)print('已结束===>deco1.wrapper1')return res1return wrapper1def deco2(func2): # func2 = wrapper3的内存地址def wrapper2(*args, **kwargs):print('正在运行===>deco2.wrapper2')res2 = func2(*args, **kwargs)print('已结束===>deco2.wrapper2')return res2return wrapper2def deco3(x): # @deco3(111)==>得到了outter函数的内存地址,@+outter语法,含义是通过调用outter函数将正下方的函数地址传入,得到的返回值赋值给原函数名def outter(func3):    # index = outter(index),故func3=被装饰对象index函数的内存地址def wrapper3(*args, **kwargs):print('正在运行===>deco3.wrapper3')res3 = func3(*args, **kwargs)print('已结束===>deco3.wrapper3')return res3return wrapper3return outter#加载顺序:自下而上(了解)
@deco1          #1、@deco1 等价于==>index=deco1(wrapper2的内存地址)=wrapper1的内存地址,即index=wrapper1的内存地址
@deco2          #1、@deco2 等价于==>index=deco2(wrapper3的内存地址)=wrapper2的内存地址,即index=wrapper2的内存地址
@deco3(111)     #1、deco3(111)等价于==> index = outter  2、@outter 等价于==> index = outter3(func)= wrapper3的内存地址 ,即index = wrapper3的内存地址
def index(x,y):print('from index %s:%s' %(x,y))return '我是最原始的index''''综上所述,index函数的内存地址被换成了wrapper1函数的内存地址,打印index查看'''
print(index)   # >输出:<function deco1.<locals>.wrapper1 at 0x7f409d9e01e0># 执行顺序自上而下的,即wraper1-》wrapper2-》wrapper3(记结论)
a = index(666,888)  # 等价==>wrapper1(666, 888) 
print('=**==**==**==**==**==**=')
print(a)

输出:<function deco1..wrapper1 at 0x7fbb3a0051e0>
正在运行===>deco1.wrapper1
正在运行===>deco2.wrapper2
正在运行===>deco3.wrapper3
from index 666:888
已结束===>deco3.wrapper3
已结束===>deco2.wrapper2
已结束===>deco1.wrapper1
===**====**=
我是最原始的index

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Echarts添加水印
  • 第八季完美童模全球人气冠军【夏沛然】荣耀加冕 见证星芒风采!
  • 【运维】Linux如何解压.rar文件
  • 微前端架构入门
  • uniapp left right 的左右模态框
  • webrtc ns 降噪之粉红噪声参数推导
  • 微软Edge浏览器
  • 2024数据泄露事件增涨迅猛,我们决不能坐以待毙!
  • day37-https实战
  • 【python数据分析11】——Pandas统计分析(分组聚合进行组内计算)
  • Nuxt3【路由中间件】middleware
  • 2024年新SCI顶刊算法信息获取优化算法IAO优化Transformer-GRU模型的多变量时间序列预测
  • 组合模式 详解
  • iPhone如何全选删除照片:一步到位的清理指南
  • 看书标记【数据科学:R语言实战 8】
  • 0基础学习移动端适配
  • CODING 缺陷管理功能正式开始公测
  • ES2017异步函数现已正式可用
  • Fastjson的基本使用方法大全
  • Git初体验
  • JS专题之继承
  • oschina
  • Spring-boot 启动时碰到的错误
  • Unix命令
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 第十八天-企业应用架构模式-基本模式
  • 聊聊flink的BlobWriter
  • 容器服务kubernetes弹性伸缩高级用法
  • 如何利用MongoDB打造TOP榜小程序
  • 时间复杂度与空间复杂度分析
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 项目管理碎碎念系列之一:干系人管理
  • 小程序01:wepy框架整合iview webapp UI
  • 写给高年级小学生看的《Bash 指南》
  • ​MySQL主从复制一致性检测
  • ​人工智能书单(数学基础篇)
  • #if 1...#endif
  • #java学习笔记(面向对象)----(未完结)
  • #Z0458. 树的中心2
  • #微信小程序:微信小程序常见的配置传旨
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (四)JPA - JQPL 实现增删改查
  • (算法设计与分析)第一章算法概述-习题
  • .mysql secret在哪_MySQL如何使用索引
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET Framework、.NET Core 、 .NET 5、.NET 6和.NET 7 和.NET8 简介及区别
  • .NET/C# 的字符串暂存池
  • .NET的微型Web框架 Nancy
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • .NET使用存储过程实现对数据库的增删改查
  • .NET是什么
  • .net中生成excel后调整宽度
  • .NET中使用Protobuffer 实现序列化和反序列化