找回密码
 会员注册
查看: 22|回复: 0

python-装饰器

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
65944
发表于 2024-9-4 17:15:05 | 显示全部楼层 |阅读模式
目录一、装饰器介绍1.为何要用装饰器2.什么是装饰器二、装饰器的实现1.无参装饰器的实现1.1.装饰器的简易版本1.2.使用语法糖实现1.3.装饰器模板1.4.双层语法糖1.5.多层语法糖1.6.装饰器修复技术(了解)1.7.装饰器之登录认证功能2.有参装饰器的实现一、装饰器介绍1.为何要用装饰器Python中的装饰器是一种语法糖,可以在运行时,动态的给函数或类添加功能。装饰器本质上是一个函数,使用@+函数名就是可实现绑定给函数的第二个功能。将一些通用的、特定函数的功能抽象成一个装饰器,可以重复利用这些功能2.什么是装饰器“装饰”代指为被装饰对象添加新的功能,“器”代指器具/工具装饰器的作用:就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。装饰器使用场景:插入日志、性能测试、事务处理、缓存、权限校验可以调用的有:函数、方法、类函数装饰器分为:无参装饰器和有参装饰,二者都是使用都是需要【名称空间+函数嵌套+闭包+函数对象的组合知识】使用“@”符号定义装饰器,前提是需要有一个函数作为工具然后被“@”装饰到其他函数头上,为这个函数添加功能二、装饰器的实现1.无参装饰器的实现1.1.装饰器的简易版本第一种:直接使用函数对象来实现importtimeprint(time.time())#1685333745.048791#1685333745叫时间戳:它是从1970年-1-1开始,到当前时间的秒数defindex():time.sleep(3)#2.调用time.sleep(3)函数,暂停程序的执行3秒钟。print('Welcometotheindexpage')#3.打印一条欢迎消息"Welcometotheindexpage"。return200#4.返回状态码200。res=index#1.执行index()函数。#5.函数执行完毕,程序继续往下执行。"""如果不懂函数体的代码该如何计算程序执行了多少秒呢"""start_time=time.time()#函数未执行前起始计算时间res()#函数执行stop_time=time.time()#函数执行之后结束时间print('runtimeis%s'%(stop_time-start_time))#结束时间减去开始时间等于函数执行时间"""考虑到还有可能要统计其他函数的执行时间,所以还要使用函数参数的形式"""*******************************************************************************************************************第二种:使用参数的形式传入defwrapper(func):#通过函数接收外部的值start_time=time.time()func()stop_time=time.time()print('runtimeis%s'%(stop_time-start_time))wrapper(index)#如果考虑到还有可能要统计其他函数的执行时间就在下方继续wrapper(其他函数)"""但是使用参数的话就改变了调用方式,违反了不能修改被装饰对象调用方式的原则,所以我们使用闭包函数,将值报给函数"""*******************************************************************************************************************第三种:使用闭包函数的形式deftimer(func):defwrapper():start_time=time.time()res=func()stop_time=time.time()print('runtimeis%s'%(stop_time-start_time))returnresreturnwrapperasd=timer(index)asd()"""以上三种方法我们就实现了无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常"""defhome(name):time.sleep(5)print('Welcometothehomepage',name)home=timer(home)home('egon')#抛出异常:TypeError:wrapper()takes0positionalargumentsbut1wasgiven"""因为home(‘egon’)调用的其实是wrapper(‘egon’),而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合,于是修正装饰器timer如下"""第四种:装饰器的进阶版本解决参数问题deftimer(func):defwrapper(*args,**kwargs):start_time=time.time()res=func(*args,**kwargs)stop_time=time.time()print('runtimeis%s'%(stop_time-start_time))returnresreturnwrapper1.2.使用语法糖实现importtimedeftimer(func):#定义一个名为timer()的装饰器函数,它接受一个参数func这个func就是即将要被修饰的函数defwrapper(*args,**kwargs):#在timer()函数内部,定义一个名为wrapper()的闭包函数start_time=time.time()#在调用wrapper()函数时,它会根据传入的参数来调用被修饰的函数func,并在函数执行前后记录时间res=func(*args,**kwargs)stop_time=time.time()print('runtimeis%s'%(stop_time-start_time))returnres#同时输出函数执行时间。最后,它将func函数的返回值返回returnwrapper@timer#使用装饰器语法来对函数进行装饰,在函数定义前加上@timer这样的语句defindex():time.sleep(1)print('Welcometotheindexpage')return200index()#调用index()函数时,会自动将其转换成timer(index)()这样的形式,将index函数作为参数传递给timer()函数,并将返回值再次作为函数调用。由于timer()函数返回了一个闭包函数wrapper(),所以最终的函数调用结果就是执行了闭包函数wrapper()"""在index()函数内部,我们调用了time.sleep(1)函数来模拟函数执行的耗时操作。然后,我们输出了欢迎信息,并返回了状态码200。执行index()函数时,由于它被装饰器修饰了,所以实际上是执行了闭包函数wrapper()。在wrapper()函数中,我们记录了函数执行前后的时间,并输出了运行时间。最后,我们将index()函数的返回值200返回给调用者。"""1.3.装饰器模板语法糖的书写规范:紧贴在被装饰对象的上方书写,语法糖语被装饰对象之间不可以有代码语法糖的内部原理:语法糖会自动把被装饰对象的名字当成参数传给装饰器函数调用#定义一个装饰器函数,它接受一个函数作为参数并返回一个新的函数defdecorator(func):#定义一个内部函数,它接受任意数量和类型的位置参数和关键字参数defwrapper(*args,**kwargs):#在调用被装饰函数之前执行的代码print("Beforecallingthefunction")#调用被装饰函数,并保存其返回值result=func(*args,**kwargs)#在调用被装饰函数之后执行的代码print("Aftercallingthefunction")#返回被装饰函数的返回值returnresult#返回内部函数,以便它可以被调用returnwrapper#定义一个函数,并使用装饰器来增加其功能@decorator#my_function=decorator(my_function)my_function是decorator函数的返回值(内部函数的函数名)defmy_function(a,b):#模拟一个运算,并返回结果result=a+breturnresult#调用被装饰函数print(my_function(1,2))执行结果:当调用my_function(1,2)时,输出结果如下:BeforecallingthefunctionAftercallingthefunction31.4.双层语法糖importtimedeflogin_auth(func):defauth(*args,**kwargs):username=input('username:').strip()password=input('password:').strip()ifusername=='kevin'andpassword=='123':print('登录成功')res=func(*args,**kwargs)returnreselse:print('用户名密码错误')returnauthdefall_time(func1):deftime1(*args,**kwargs):start_time=time.time()res=func1(*args,**kwargs)stop_time=time.time()print('功能执行了%s秒'%(stop_time-start_time))returnresreturntime1@all_time#index=all_time(all_time内部的time1的函数名)当装饰器执行到最后一层这个变量就使用跟最初装饰器同名的函数名同名@login_auth#all_time内部的time1的函数名=login_auth(index)语法糖会把被装饰对象的函数名当成第一个参数传给装饰器的第一个参数defindex():print('执行完成')index()#最后一层就是auth()1.5.多层语法糖defoutter1(func1):print('加载了outter1')defwrapper1(*args,**kwargs):print('执行了wrapper1')res1=func1(*args,**kwargs)returnres1returnwrapper1defoutter2(func2):print('加载了outter2')defwrapper2(*args,**kwargs):print('执行了wrapper2')res2=func2(*args,**kwargs)returnres2returnwrapper2defoutter3(func3):print('加载了outter3')defwrapper3(*args,**kwargs):print('执行了wrapper3')res3=func3(*args,**kwargs)returnres3returnwrapper3@outter1#index(outter1的内层函数名,一般最后一层我们使用跟功能函数同名的函数名index)=outter1(wrapper2)---------------wrapper1@outter2#wrapper2(outter2的内层函数名)=outter2(wrapper3)@outter3#wrapper3(outter3的内层函数名)=outter3(index)"""只要加了语法糖,装饰器一定会执行,不调用被装饰的函数也会执行装饰器执行装饰器是从下往上执行,执行到最后一个装饰器之后,会把最后一个装饰器的返回值的变量名语与被装饰对象的函数名同名语法糖不会在没有调用装饰对象之前,是不会执行内部函数的"""defindex():print('fromindex')index()#只要不调用被装饰的函数就不会执行装饰器内层函数执行顺序分析:加载了outter3加载了outter2加载了outter1执行了wrapper1执行了wrapper2执行了wrapper3fromindex执行顺序原理解释如下:1.定义outter3函数时,先打印出"加载了outter3";2.进入outter2函数,先打印出"加载了outter2";3.在outter2函数中创建了wrapper2函数并返回,但并不执行wrapper2函数,此时将wrapper2函数作为参数传递给@outter1装饰器;4.进入outter1函数,先打印出"加载了outter1";5.在outter1中将wrapper2函数作为参数传递给outter1的内部函数wrapper1,并返回wrapper1函数,此时index函数被全局变量所指向;6.当调用index()函数时,实际上是调用了经过装饰器增强后的wrapper1函数;7.调用wrapper1函数,首先打印出"执行了wrapper1",然后调用wrapper2函数;8.调用wrapper2函数,首先打印出"执行了wrapper2",然后调用wrapper3函数;9.调用wrapper3函数,首先打印出"执行了wrapper3",然后执行函数体中的print('fromindex')语句;最终输出函数的执行结果"fromindex"。1.6.装饰器修复技术(了解)装饰器通常会将一个函数替换成另一个函数,修改函数的属性,或者在函数周围添加一些代码来实现某些功能,这可能会导致函数元数据信息的丢失,比如函数名、文档字符串、参数列表等等,为了避免这种情况,可以使用wraps装饰器来修饰装饰器本身,从而确保被修饰的函数保留了其原有的元数据信息不变。wraps装饰器本身也是一个装饰器,它接受一个函数作为参数,并返回一个包装函数。wraps装饰器会将包装函数的名称、文档字符串、参数列表等元数据信息更新为原函数的元数据信息,从而确保被修饰的函数可以正确地保留原有的元数据信息。使用方法:导入fromfunctoolsimportwraps在装饰器的外部与内部函数之间定义@wraps(func)wraps装饰器确保了被装饰的函数say_hello保留了其原本的元数据信息,包括函数名和文档字符串。这样,即使在嵌套多个装饰器的情况下,也能够正确地保留函数的属性信息。fromfunctoolsimportwrapsdefmy_decorator(func)wraps(func)defwrapper(*args,**kwargs):print('Beforethefunctioniscalled.')result=func(*args,**kwargs)print('Afterthefunctioniscalled.')returnresultreturnwrapper@my_decoratordefsay_hello(name):"""Afunctionthatsayshello"""print(f'Hello,{name}!')print(say_hello.__name__)#输出'say_hello'print(say_hello.__doc__)#输出'Afunctionthatsayshello''运行运行1.7.装饰器之登录认证功能is_login={'is_login':False}#判断如果有一个函数成功等候后面的函数就不需要登录了deflogin_auth(func):deflogin(*args,**kwargs):username=input('username:').strip()password=input('password:').strip()ifusername=='kevin'andpassword=='123':print('登录成功')res=func(*args,**kwargs)is_login['is_login']=True#只要is_login为True就不会登录returnreselse:print('用户名或者密码错误')returnlogin@login_authdefuser_login():print('认证成功')res=user_loginres()2.有参装饰器的实现有参装饰器本质上就是在普通装饰器头上再嵌套一层函数用来传递参数当我们使用最外层的函数传递参数是,语法糖后面的括号中也需要传递相应的参数#定义一个带参数的装饰器函数repeat(),它接受一个整数类型的参数count,表示被修饰函数需要重复执行的次数:defrepeat(count):defdecorator_func(func):defwrapper_func(*args,**kwargs):foriinrange(count):res=func(*args,**kwargs)returnresreturnwrapper_funcreturndecorator_func"""在repeat()函数内部,定义了一个闭包函数decorator_func(),用于接受被修饰函数func。在decorator_func()函数内部,再定义了一个闭包函数wrapper_func(),用于实现装饰器的具体功能。在wrapper_func()函数内部,使用一个循环来控制被修饰函数的执行次数。在每次循环中,我们尝试执行被修饰函数,并返回其结果。最终,我们将wrapper_func()函数作为修饰器的返回值。"""@repeat(3)defgreet(name):print("Hello,%s!"%name)"""当我们执行greet('Alice')函数时,它实际上已经被修饰成了repeat(3)(greet)('Alice')这样的形式,即先将greet函数传递给decorator_func(),再将返回的wrapper_func函数作为结果返回,并在其基础上执行函数greet('Alice')。由于装饰器repeat()是一个带参数的装饰器,所以要在装饰器名称后面加上括号并传入参数值,来指定装饰器的参数值。在这个例子中,我们使用@repeat(3)来指定函数greet()需要重复执行3次。"""res1=greetres()Hello,Alice!Hello,Alice!Hello,Alice!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-31 04:20 , Processed in 0.488406 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表