共计 8821 个字符,预计需要花费 23 分钟才能阅读完成。
一. 什么是装饰器
-
器 : 就是工具
-
装饰 : 就是添加新功能
'总结一句话来说' : 就是定义一个函数, 用该函数去为其他函数添加新功能
二. 为何要使用装饰器
1. 开放封闭原则
- 针对上线的功能对拓展是开放的
- 但是对修改源代码以及调用方式是封闭的
'总结来说' : 装饰器就是在遵循开放封闭原则的前提下为被装饰对象添加新功能
🐳不修改被装饰对象的源代码
🐳不修改被装饰对象的调用方式
2. 在实际应用场景原因
- 对上线软件的拓展功能, 我们必须提供扩展的可能性
- 软件的源代码以及调用方式都应该避免被修改, 否则上线出现 BUG 之后不容易回滚
3. 装饰器与被装饰的对象均可以是任意可调用的对象
- 装饰器 -----> 函数
- 被装饰的对象 -----> 函数
三. 无参装饰器的优化历程 (助于理解)
1. 无参装饰器
需求 : 为 index 函数加上计时功能
import time # time 模块
#time.time() 返回的是时间戳, 从 1970 年 1 月 1 日凌晨到现在的秒数
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 的内存地址)
🐳🐳结果 : 在上一个版本的基础上把被装饰的对象写活了, 可以装饰任何无参函数, 但还是改变了调用方式
- 版本五 : 优化版本
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", 现在还是怎么使用
🐳🐳结果 : 没有改变调用方式, 没有改变源代码, 还实现了功能
2. 无参装饰器的雏形
以上版本对于不需要参数传入的函数使用没有问题, 但对有参数传入函数进行装饰就会报错
- 基于上面的版本进行进一步的优化,
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
🐳🐳结果 : 完美!
四. 无参装饰器的最终结果及应用
1.🍭无参装饰器语法糖
🍭最精简的装饰器, 将需要的功能往里面添加就行
def outter(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
@outter #被装饰函数的正上方单独一行
def test():
pass
2. 计算时间示例
🐳"@" 的作用就是将 "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') # 调用该怎么调还是怎么调
3. 认证用户示例
🐳实现的功能就是用户登入了才能使用 "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 #index=auth(index)
def home(name):
time.sleep(2)
print('welcome %s to home page' %name)
index()
home('egon')
4. 多个装饰器组合使用示例
- 多个装饰器组合使用时, 加载顺序是自下而上, 而执行顺序是自上而下的 (下面 第七大段 会详细解析)
🐳实现用户认证, 还可以计算程序运行的时间
##############1. 用户认证装饰器 ##################
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
###############2. 计算时间装饰器 ###################
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
################3. 开始装饰 ###################
🐳有前后顺序, 这里计算 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')
五. 有参装饰器的实现
1.🍭有参装饰器语法糖
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
2. 有参装饰器示例
- 需求 : 如果我们的装饰器也需要参数传入呢? 这该如何解决
🐳观察下面, 装饰器函数内部需要传入一个值 "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 装饰器
1.wraps 有什么作用
- 被装饰的函数 的一些属性值赋值给 装饰器函数(wrapper),最终让属性的显示更符合我们想看到的 (装的更像)
2. 先看未添加 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 函数, 只不过是将其改了下函数名
3. 添加 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
'''
# 可以发现属性已经发生了传递, 装的更像了👻
七. 叠加多个装饰器的底层原理分析
- 叠加多个装饰器的 加载 顺序是 自下而上 的(了解)
- 叠加多个装饰器的 执行 顺序是 自上而下 的
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
'''
叠加多个装饰器示例在本文 第四段第四小节
正文完