python之装饰器
引入
开放封闭原则:
-
开放:对代码的拓展新功能开放。
-
封闭:对源码的修改以及调用方式的修改封闭。
一、什么是装饰器
拆词分析
-
‘装饰’: 指的是为被装饰对象添加的新功能
-
‘器’: 指的是工具. 也可以理解为定义一个函数
合到一起总结(重点):
-
装饰器指的就是定义一个函数, 用该函数去为其他函数添加新功能
二、为何要用装饰器?
软件的设计应该遵循开放封闭原则:
-
软件的设计应该遵循开放封闭原则, 即对外扩展是开放的, 对修改是封闭的.
-
对扩展开放: 不修改被装饰对象的源代码
-
对修改封闭: 不修改被装饰对象的调用方式
-
开放封闭原则总结(重点):
-
开放: 拓展新功能开放
-
封闭: 修改源代码封闭
-
装饰器的作用(重点):
不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加新功能.
使用装饰器的原因:
-
原因一: 软件包含的所有功能的源代码和调用方式, 都应该避免被修改, 否则一旦改错, 极有可能产生连锁反应, 最终导致程序奔溃.
-
原因二: 上线后的软件, 新需求或者变化层出不穷, 我们必须提供扩展的可能性
装饰器与被装饰的对象均可以是任意可调用的对象
-
装饰器—–>函数
-
被装饰的对象—–>函数
三、无参装饰器的优化历程 (助于理解)
引入:
time
模块下的 time.time()
功能
import time
print(time.time()) # time.time()返回的值是时间戳. 以1970年作为起始, 到现在时间为结束. 单位秒
# 历史补充: 1970年是unix元年
无参装饰器
需求:不修改index源代码和调用方式的前提下为index添加统计时间的功能
import time #time模块
def index():
time.sleep(1)
print("from index")
index()
方案一:
直接在函数体内设置计时
import time
def index():
start=time.time() #开始时间
time.sleep(1) #睡"1"秒
print('from index')
stop=time.time() #结束时间
print('run time is %s' %(stop - start)) #运行时间
index()
# 结果 : 没有修改调用方式,但修改了源代码
方案二:
在调用函数的阶段进行计算时间
import time
def index():
time.sleep(1)
print('from index')
start=time.time()
index()
stop=time.time()
print('run time is %s' %(stop - start))
# 结果 : 没有修改调用方式,没有修改源代码,但代码冗余,需要写很多重复代码
方案三:
使用闭函数的方式在wrapper
函数里面调用index
import time
def index():
time.sleep(1)
print('from index')
def wrapper():
start=time.time()
index()
stop=time.time()
print('run time is %s' %(stop - start))
wrapper()
# 结果 : 解决了代码冗余的问题,但把"index"写死了,只能计算"index"的运行时间
方案三的优化一:
将index
当做参数传入
import time
def index():
time.sleep(1)
print('from index')
def index2():
time.sleep(1)
print('from index2')
def wrapper(f):
start=time.time()
f() # 函数index的内存地址()
stop=time.time()
print('run time is %s' %(stop - start))
wrapper(index) # wrapper(函数index的内存地址)
wrapper(index2) # wrapper(函数index2的内存地址)
# 结果 : 在上一个版本的基础上把被装饰的对象写活了,可以装饰任何无参函数,但还是改变了调用方式
方案三的优化二:
在优化一的基础之上, 把被装饰对象写活了, 原来只能装饰index
import time
def index(x, y, z):
time.sleep(1)
print('from index', x, y, z)
def outer(func):
# func = index
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs) # index(1, 2 3) index(1, z=3, y=2)
stop_time = time.time()
print('index is run time:', start_time - stop_time)
return wrapper
# 装饰之前index
print(index) # <function index at 0x000002533719CEE0>
index = outer(index) # warpper = outer(index)
# 装饰之后index
print(index) # <function outer.<locals>.wrapper at 0x00000253371AA040>
index(1, 2, 3) # warpper(1, 2, 3) --> func(1, 2, 3)
index(1, z=3, y=2) # wrapper(1, z=3, y=2) --> func(1, z=3, y=2)
方案三的优化三:
import time
def index():
time.sleep(1)
print('from index')
def outter(f): # f=函数index的内存地址
def wrapper():
start=time.time()
f() # 函数index的内存地址()
stop=time.time()
print('run time is %s' %(stop - start))
return wrapper
index=outter(index) # outter(函数index的内存地址)
index() #?偷梁换柱法,让使用者感觉不到变化,之前怎么使用"index",现在还是怎么使用
# 结果 : 没有改变调用方式,没有改变源代码,还实现了功能
无参装饰器的雏形
以上版本对于不需要参数传入的函数使用没有问题, 但对有参数传入函数进行装饰就会报错
基于上面的版本进行进一步的优化
def home(name):
time.sleep(2)
print('home page,welecome %s' %name)
def outter(f): # f=函数home的内存地址
def wrapper(name): # 设置形参可进行传值
start=time.time()
f(name) # 函数home的内存地址(name)
stop=time.time()
print('run time is %s' %(stop - start))
return wrapper
home=outter(home) # outter(函数home的内存地址)
home("egon") # 偷梁换柱
# 结果 : 设置形参后可以进行传值,但也就只能对"home"这个函数进行装饰了,写死了!
优化 版本:
可以传入任意的参数, 也可以不传参数, 利用可变长参数(*args
)与(**kwargs
)来实现
前面函数参数已经对可变长参数(*args
)与(**kwargs
)进行了详细介绍,可以参考
import time
def index():
time.sleep(1)
print('from index')
def home(name):
time.sleep(2)
print('home page,welecome %s' %name)
def outter(f): # f=函数home的内存地址
def wrapper(*args,**kwargs):
start=time.time()
f(*args,**kwargs) # 函数home的内存地址(name)
stop=time.time()
print('run time is %s' %(stop - start))
return wrapper
index=outter(index) # outter(函数index的内存地址)
index() # 偷梁换柱
home=outter(home) # outter(函数home的内存地址)
home("egon") # 偷梁换柱
# 结果 : 实现了可以装饰无参和有参,并且遵循了开放封闭原则,但是还有一个问题:就是没有返回值!
最终优化版本(终极不修改版):
设置返回值
import time
def index():
time.sleep(1)
print('from index')
def home(name):
time.sleep(2)
print('home page,welecome %s' %name)
return 123 #有返回值的
def outter(f): # f=函数home的内存地址
def wrapper(*args,**kwargs):
start=time.time()
res=f(*args,**kwargs) # 函数home的内存地址(name),并接收返回值
stop=time.time()
print('run time is %s' %(stop - start))
return res # 设置返回值
return wrapper
index=outter(index)
home=outter(home)
res=index()
print(res) # None
res=home("egon")
print(res) # 123
# 结果 : 完美!
标准无参装饰器的格式(模板):
def wrapper(f):
def inner(*args,**args):
'''被装饰函数执行前添加的功能'''
ret = f(*args,**args)
'''被装饰函数执行后添加的功能'''
return ret
return inner
四、装饰器的语法糖
装饰器语法糖
格式:
def wrapper(func):
pass
@wrapper
# 注释1
# 注释2
def f():
pass
必须在装饰器定义后,使用语法糖。而且是在被装饰函数的定义语句正上方使用,否则会报错。在语法糖和被装饰函数之间只能用#
号加注释,不能用三对单引号或双引号,会报语法错误。
解释器在执行到语法糖@wrapper时会立即调用装饰器wrapper,然后将正下方的被装饰函数,当做参数传给wrapper,将返回值赋值给原函数名,语法糖固定就一个参数。
示例:
import time
def wrapper(func):
def inner(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print(stop - start)
return res
return inner
@wrapper # 相当于 f1 = wrapper(f1)
def f1(x,y):
c = 0
for i in range(x):
for n in range(y):
c = i + n + c
return c
print(f1(1000,10000)) # 此时f1已经被狸猫换太子,其实是inner(1000,10000)
0.8780503273010254
54990000000
五、无参装饰器的最终结果及应用
无参装饰器语法糖
# 最精简的装饰器, 将需要的功能往里面添加就行
def outter(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
@outter #被装饰函数的正上方单独一行
def test():
pass
计算时间示例
"@"的作用就是将"index"的内存地址传递到"timmer"中?"func=index"
import time
def timmer(func):
def wrapper(*args,**kwargs):
start_time=time.time() #开始时间
res=func(*args,**kwargs)
stop_time=time.time() #结束时间
print(stop_time-start_time) #使用时间
return res
return wrapper
@timmer # index=timmer(index)
def index():
time.sleep(1)
print('welcome to index page')
return 122
@timmer # home=timmer(home)
def home(name):
time.sleep(2)
print('welcome %s to home page' %name)
index() # 调用该怎么调还是怎么调
home('egon') # 调用该怎么调还是怎么调
用户认证示例
实现的功能就是用户登入了才能使用"index"或者"home"
import time
current_user={
'username':None, #判断用户是否已经登入,有值就是已登入,"None"就是未登入
}
def auth(func):
def wrapper(*args,**kwargs):
if current_user['username']: # 这里开始判断了
print('已经登陆过了')
res=func(*args,**kwargs) #如果已经登入就直接运行被装饰的函数
return res
uname=input('用户名>>: ').strip() # 如果未登入则输密码
pwd=input('密码>>: ').strip()
if uname == 'egon' and pwd == '123':
print('登陆成功')
current_user['username']=uname
res=func(*args,**kwargs)
return res
else:
print('用户名或密码错误')
return wrapper
@auth #index=auth(index)
def index():
time.sleep(1)
print('welcome to index page')
return 122
@auth
def home(name):
time.sleep(2)
print('welcome %s to home page' %name)
index()
home('egon')
六、有参装饰器的实现
有参装饰器语法糖
def outter2(x):
def outter(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
return outter
@outter2(11) # 被装饰函数的正上方单独一行
def hhh():
pass
有参装饰器模板
由于语法糖的关系,wrapper
的参数不能改变,而inner
的参数是接收被装饰函数的参数,也不能改变,那么若要向装饰器传参,则需要再最外层再定义一个函数,以此来接收传给装饰器的参数。
def outer(x,y,z):
def wrapper(func):
def inner(*args,**kwargs):
'''被装饰函数执行前添加的操作'''
ret = func(*args,**kwargs)
'''被装饰函数执行后添加的操作'''
return ret
return inner
return wrapper
此时则实现了一个有参装饰器,在被装饰函数定义语句上方使用:
@outer(x,y,z)
# 装饰器outer括号内的参数可以按需求定义,*args,**kwargs都可以。
-
需求: 如果我们的装饰器也需要参数传入呢? 这该如何解决
# 观察下面,装饰器函数内部需要传入一个值"xxx",从哪里来?
def outter(func):
def wrapper(*args,**kwargs):
if xxx == "login":
res=func(*args,**kwargs)
return res
elif xxx == "login2":
print("22222")
elif xxx == "login3":
print("33333")
return wrapper
-
我们只需要在外面包一层就行了
简单语法
def auth(xxx): #再包一层
def outter(func):
def wrapper(*args,**kwargs):
if xxx == "login":
res=func(*args,**kwargs)
return res
elif xxx == "login2":
print("22222")
elif xxx == "login3":
print("33333")
return wrapper
return outter #同时也要有返回值
-
有参装饰器示例
# 通过不同的选择进行不同的身份认证(比如商家认证,买家认证等等) import time dic = { 'username': None, } def auth(engine): def auth2(func): def wrapper(*args, **kwargs): if engine == 'login1': #如果传入的是"login1",就进入这种身份的认证 if dic['username']: print('已经登陆过了') res = func(*args, **kwargs) return res name = input('用户名>>: ').strip() pwd = input('密码>>: ').strip() if name == 'egon' and pwd == '123': print('登陆成功') dic['username'] = name res = func(*args, **kwargs) return res else: print('用户名或密码错误') elif engine == 'login2': #"login2"这种模式的身份认证 print('xx认证') elif engine == 'login3': #"login3" print('xxxxxx认证') return wrapper return auth2 @auth('login3') #以"login3"模式的身份认证来使用"index" def index(): time.sleep(1) print('欢迎登入') return 123 @auth('login1') #以"login1"模式的身份认证来使用"home" def home(song): time.sleep(2) print('啧啧啧%s' % song) index() home('哈哈')
七、wraps装饰器
作用
为了将装饰后的函数伪装的与被装饰函数更相似,Python有个内置的装饰器wraps,能使调用者在查看装饰后函数的内置属性时,看似与原函数一致。
先看未添加 wraps 装饰器时的属性
-
help() : 既查看函数注释文档有查看函数名
-
.name : 查看函数名
-
.doc : 查看函数的注释文档
import time
def timmer(func):
def wrapper(*args,**kwargs):
"我是wrapper函数注释信息" # 这里添加了wrapper的注释信息
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print(f"运行时间{stop-start}秒")
return res
return wrapper
@timmer
def hhh():
"hhh函数注释信息" ## 这里添加了hhh的注释信息
print("欢迎欢迎")
print(hhh.__name__)
# 返回的是 wrapper 的函数名 "wrapper"
print(hhh.__doc__)
# 返回额是 wrapper 的注释信息 "我是wrapper函数注释信息"
print(help(hhh))
'''依然是 wrapper 的注释和名字
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
我是wrapper函数注释信息
None
'''
# 这不值得奇怪,因为装饰器的底层原理就是返回了 wrapper 函数,只不过是将其改了下函数名
添加 wraps 装饰器
-
通过添加 wraps 装饰器将原函数 hhh 的属性传给 wrapper 函数
# 注意与上面示例做对比 import time from functools import wraps #导入模块 def timmer(func): @wraps(func) #在 wrapper 的正上方添加wraps装饰器,把原有函数的属性传给wrapper def wrapper(*args,**kwargs): "我是wrapper函数注释信息" start = time.time() res = func(*args,**kwargs) stop = time.time() print(f"运行时间{stop-start}秒") return res return wrapper @timmer def hhh(): "hhh函数注释信息" print("欢迎欢迎") print(hhh.__name__) # hhh print(hhh.__doc__) # hhh函数注释信息 print(help(hhh)) ''' Help on function hhh in module __main__: hhh() hhh函数注释信息 None ''' # 可以发现属性已经发生了传递,装的更像了?
wraps装饰器 实例:
import time
from functools import wraps
def timmer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print('run time is:%s' % (stop - start))
return res
# wrapper.__name__=func.__name__
# wrapper.__doc__=func.__doc__
return wrapper
@timmer # index= timmer(index) # index=wrapper
def index():
"index函数"
time.sleep(1)
print('from index')
print(index) # 此时index实际上是wrapper内存地址。
print(index.__name__) # 查看函数名
help(index) # 查看函数注释信息。
结果:
<function index at 0x0000000002788DC0>
index
Help on function index in module __main__:
index()
index函数
如果将wraps注释掉:
<function timmer.<locals>.wrapper at 0x0000000002788DC0>
wrapper
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
# @wraps(func)
可以看到内存地址并没有变化,但名字都改变了。
八、叠加多个装饰器的低层原理分析
-
叠加多个装饰器的加载顺序是自上而下的(了解)
-
叠加多个装饰器的执行顺序是自上而下的
def outter1(func1): # func1 = wrapper2的内存地址
print('============>outter1')
def wrapper1(*args,**kwargs):
print('============>wrapper1')
res1=func1(*args,**kwargs)
return res1
return wrapper1
def outter2(func2): # func2 = wrapper3的内存地址
print('============>outter2')
def wrapper2(*args,**kwargs):
print('============>wrapper2')
res2=func2(*args,**kwargs)
return res2
return wrapper2
def outter3(func3): # func3 = 被装饰函数也就是index的内存地址
print('============>outter3')
def wrapper3(*args,**kwargs):
print('============>wrapper3')
res3=func3(*args,**kwargs)
return res3
return wrapper3
?加载顺序自下而上,先是"outter3"开始将"index"传入自己内部,然后"outter2"把"outter3"的返回值传入自己...
@outter1 # outter1(wrapper2)---->wrapper1的内存地址 (index=wrapper1)
@outter2 # outter2(wrapper3)---->wrapper2的内存地址 (index=wrapper2)
@outter3 # outter3(index)------->wrapper3的内存地址 (index=wrapper3)
def index():
print('from index')
#只加载不执行输出结果
'''
============>outter3
============>outter2
============>outter1
'''
#加载执行
index()
'''
============>outter3
============>outter2
============>outter1
============>wrapper1
============>wrapper2
============>wrapper3
from index
'''
多个装饰组合使用示例
# 实现用户认证,还可以计算程序运行的时间
##############用户认证装饰器##################
import time
dic = {
'username': None,
}
def login(func):
def wrapper(*args, **kwargs):
if dic['username']:
print('已登入')
res = func(*args, **kwargs)
return res
name = input('姓名>>:').strip()
pwd = input('密码>>:').strip()
if name == 'song' and pwd == '123':
print('登入成功')
dic['username'] = name
res = func(*args, **kwargs)
return res
else:
print('用户名或密码错误')
return wrapper
##############计算时间装饰器###################
def timer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('时间%s'%(stop_time-start_time))
return res
return wrapper
##############开始装饰###################
# 有前后顺序,这里计算 login()+index() 的时间(也就是加上了认证的时间)
@timer # timer在前
@login
def index():
time.sleep(1)
print('欢迎淘小欣')
return 125
# 只计算程序的运行时间(正确顺序)
@login
@timer #这里只计算 home() 的时间
def home(name):
time.sleep(2)
print('欢迎%s' %name)
index()
home('hai')