回顾内容:
- 函数可以作为参数进行传递
- 函数可以作为返回值
- 函数名称可以像变量一样进行赋值操作
装饰器:要求记住结论
引入:
def play_dnf():
print("你好啊,我是赛利亚,今天又是美好的一天")
def play_lol():
print("德玛西亚")
print("开挂")
play_dnf()
print("关闭外挂")
print("开挂")
play_lol()
print("关闭外挂")
太麻烦,聘请管家帮我开启外挂
#函数作为参数进行传递
def guanjia(fn):
print("开挂")
fn()
print("关闭外挂")
def play_dnf():
print("你好啊,我是赛利亚,今天又是美好的一天")
def play_lol():
print("德玛西亚")
guanjia(play_dnf)
但成了管家在打游戏了
解决办法:
#函数作为参数进行传递
def guanjia(fn):
def inner():
print("开挂")
fn()
print("关闭外挂")
return inner
def play_dnf():
print("你好啊,我是赛利亚,今天又是美好的一天")
def play_lol():
print("德玛西亚")
play_dnf = guanjia(play_dnf)
#让管家把游戏重新封装了,我这边把原来的游戏替换掉
play_dnf() #此时运行的是内部函数inner
play_lol = guanjia(play_lol)
play_lol()
还是太麻烦:
#函数作为参数进行传递
def guanjia(fn):
def inner():
print("开挂")
fn()
print("关闭外挂")
return inner
@guanjia #相当于play_dnf = guanjia(play_dnf)
def play_dnf():
print("你好啊,我是赛利亚,今天又是美好的一天")
@guanjia #相当于play_lol = guanjia(play_lol)
def play_lol():
print("德玛西亚")
play_dnf()
play_lol()
本质
装饰器本质上是一个闭包,作用是在不改变原有函数的前提下,为函数添加新的功能,可以在函数的前后添加新的功能,但是源代码不改变
运用:
- 在用户登录的地方
- 日志
- ……
def wrapper(目标函数):
def inner:
之前,添加事情
目标函数执行 fn()
之后,添加功能
return inner #千万别加()
@wrapper # 目标函数 = wrapper(目标函数)
__name__属性:帮助获取函数的名字
例:
def now():
print("1111111111111")
a = now #a是now的别名
b = a #b是a的别名
#若别名套用太多,不知道别名是哪个函数的
print(b.__name__) #获取别名本身的函数名字
结果:
now
还可以:
def guanjia(fn):
print(fn.__name__) #看fn是指向哪个函数
练习:
记录玩游戏的时间(装饰器)
import time #引入时间模块(后面会讲)
time.time() #时间戳:指格林威治时间1970年01月01日00时00分00秒(北京时间 1970年01月01日08时00分00秒)起至现在的总秒数
time.sleep(x) #沉睡x秒
例:
import time
def time_01(fn):
def inner():
start_time = time.time()
fn()
end_time = time.time()
execution_time = end_time - start_time
print(f"{fn.__name__}函数的执行时间是{execution_time}")
return inner
@time_01
def test1():
time.sleep(3)
print("1111")
test1()
结果:
1111
test1函数的执行时间是3.002014398574829
因为装饰器是将 目标函数 = wrapper(目标函数)
即 目标函数 = wrapper(目标函数)========>wrapper.inner
所以我们要传参的话inner(x,x)需要有形参接收
例:
def inner(name,pwd):
但装饰器如果装饰不同函数的话,不同函数的形参需求不同的话还是会报错
例:
@guanjia
def play_wz(uname,pwd):
@guanjia
def play_lol(uname,pwd,area):
所以我们可以用可变参数来解决(*args, **kwargs)
def guanjia(fn):
def inner(*args, **kwargs):#* ,** 表示借助所有传进来的实参,打包成元组和字典
print("开挂")
fn(*args, **kwargs)#*,**表示把元组和字典打散成位置参数以及关键字参数传递进去
print("停止游戏,关闭外挂")
return inner
@guanjia # 相当于play_wz=gunajia(play_wz)
def play_wz(uname, password):
print(f"用户名是{uname},密码是{password}")
print("来和妲己玩耍吧")
@guanjia
def play_cj(uname, password, hero):
print(f"用户名是{uname},密码是{password},英雄是{hero}")
print("注意标记点")
play_wz('gouxin', 123456)
play_cj("gouxin", 123456, 'daji')
play_cj(uname = "lisi",password ="123456",hero ="nanjing")
结果:
开挂
用户名是gouxin,密码是123456
来和妲己玩耍吧
停止游戏,关闭外挂
开挂
用户名是gouxin,密码是123456,英雄是daji
注意标记点
停止游戏,关闭外挂
开挂
用户名是lisi,密码是123456,英雄是nanjing
注意标记点
停止游戏,关闭外挂
添加**kwargs的原因是:
如果有人传参时是用关键字形式传参,只写*args就会报错(如下)
def inner(*args):
play_cj(uname = "lisi",password ="123456",hero ="nanjing")
如果游戏函数结束后要返回一个值,这怎么解决呢?
直接上代码(注释讲解)
def guanjia(fn):
def inner(*args,**kwargs):
print("开挂")
ret = fn(*args,**kwargs) #用ret接收返回的值
print("关闭外挂")
return ret #再将ret返回出去,作为inner(即play_wz)的返回值
return inner
@guanjia # play_wz = guanjia(play_wz)
def play_wz(uname,pwd):
print("来和妲己玩耍啊~")
print(f"用户名是{uname},密码是{pwd}")
return "妲己66666" #运行完返回一个值
@guanjia
def play_lol(uname,pwd,area):
print("德玛西亚")
print(f"用户名是{uname},密码是{pwd},地区是{area}")
a = play_wz("zs","123456") #用a来接收返回值
print(a) #打印返回值(即游戏的返回值)
play_lol(uname = "lisi",pwd ="123456",area ="nanjing")
结果:
开挂
来和妲己玩耍啊~
用户名是zs,密码是123456
关闭外挂
妲己66666 #结束后返回的值
开挂
德玛西亚
用户名是lisi,密码是123456,地区是nanjing
关闭外挂
如果一个函数可以被多个装饰器装饰,是如何运行的呢?
def wrapper1(fn):
def inner(*args,**kwargs):
print("第一个装饰器")
fn()
print("第一个装饰器结束")
return inner
def wrapper2(fn):
def inner(*args,**kwargs):
print("第二个装饰器")
fn()
print("第二个装饰器结束")
return inner
@wrapper1 #test = wrapper1(test)=wrapper1(wrapper2.inner)=====>wrapper1.inner
@wrapper2 #test = wrapper2(test)========>wrapper2.inner
def test():
print("我是函数")
test()
结果:
第一个装饰器
第二个装饰器
我是函数
第二个装饰器结束
第一个装饰器结束
先装饰函数的上面的一层(即wrapper2),再依次往上装饰(即wrapper1)
最后运行test( )即运行wrapper1.inner
所以可以看结果来理解运行顺序