装饰器
定义:本质是函数,(装饰其他函数),就是为其他函数添加附加功能。
原则:
-
不能修改被装饰的函数的源代码
-
不能修改被装饰的函数的调用方式
-
不能影响原函数的返回值
公式:高阶函数 + 嵌套函数 + 运用函数即变量的思想 = 装饰器
示例:
#装饰器,为函数test加上计时功能
def timer(func):
def secend(*name):
start_time = time.time();
res = func(*name)
end_time = time.time();
print("运行时间为%s秒"%(end_time - start_time))
return res
return secend
@timer
def test():
time.sleep(1)
print("this is test func")
-----------------------------------------------------------------------分割线---------------------------------------------------------------------------
在搞清楚装饰器之前,我们需要理解3个东西:
1、函数即“变量”:
函数(名)可以相当于一个变量,可以进行赋值等等操作,实际上函数名只是函数内容的存放地址,可以把它等价为 x = y 这样赋值(假设x,y均为函数名)。
2、高阶函数:
高阶函数形式有两种:
(1)把一个函数名当作实参传给另一个函数(为不修改被装饰函数源代码的情况下为其添加功能做准备)。
(2)返回值是一个函数名或者返回值中包含函数名(为不修改函数的调用方式做准备)。
3、嵌套函数
就是函数内部又有一个函数,比如下面:
#嵌套函数
def a():
print('a')
def b():
print('b')
print('bbb')
b() #调用b函数
a() #调用a函数
了解上面三个知识点后。
记住一个公式:高阶函数 + 嵌套函数 + 运用函数即变量的思想 = 装饰器
现在写一个装饰器,用来计算那个函数要花的时间。
注意:1、装饰器要写在原函数前面,所以先看test原函数,再看timer装饰器。
2、始终理解:高阶函数 + 嵌套函数 + 运用函数即变量的思想 = 装饰器。
import time
'''装饰器,计算test花费时间'''
def timer(func): #函数可以作为参数传进另一个函数(高阶函数),第一层传入一个函数
def secend(): #第二层函数加入新的功能
start_time = time.time();
res = func() #调用原来的函数(func()中:func代表传入的那个函数,右边写上一对小括号等于调用的意思),返回值用res接收
end_time = time.time();
print("运行时间为%s秒"%(end_time - start_time)) #打印耗时
return res #这里用res接收func的返回值并return,确保不遗漏返回值
return secend
'''原函数'''
# @timer #等价于这句话:test = timer(test),也就是test= secend,现在test() = secend()
def test():
time.sleep(1)
print("this is test func")
return 1
'''
timer(test)的返回值是secend,(不带括号),也就是新写出来的函数的内容的地址,包括了新的功能,赋值给原来的test替换掉
(函数即变量),在运行原来的test,就可以使用到装饰器的功能,并且调用test()的地方不用删除,只需要增加一句话,也不改变原
来函数里面的内容。但因为这样改动麻烦(要在所有调用过test的地方改),所以python给了一种方法:在test函数头加上一句:@timer,
在下面会介绍到。
'''
test = timer(test)
test()
运行结果为:(没有改变原来的功能和调用方式)
代码里的注释都有详细的解释,可自行查看。
直接在要加装饰器的函数头加上{@装饰器}是一样的效果。这样省去了在每次调用的地方改函数
因为要让大家理解一下函数的赋值问题,所以上面用了test = timer(test)这句代码。
完成上面代码之后,我们会发现,当我们要去装饰的函数有的有参数,有的没有参数的时候,这个时候会出现错误:
因为装饰器不能吧“LIKUNKUN ”这个字符串参数接收到。
之前说到,调用testWithParameter就等于调用secend(),所以只需要在secend(加入参数)就可以了,改动这一部分代码:
#装饰器
def timer(func):
def secend(name):
start_time = time.time();
res = func(name)
end_time = time.time();
print("运行时间为%s秒"%(end_time - start_time))
return res
return secend
但这样的话,之前的test代码又运行不了了,因为test并没有参数,于是python提供给参数前面加上*,表示可选参数
改动之后不管一个参数,两个参数,没有参数,都可以运行了:
#装饰器
def timer(func):
def secend(*args,**kwargs):
start_time = time.time();
res = func(*args,**kwargs)
end_time = time.time();
print("运行时间为%s秒"%(end_time - start_time))
return res
return secend
@timer
def test():
time.sleep(1)
print("this is test func")
@timer #testWithParameter = timer(testWithParameter)
def testWithParameter(name):
time.sleep(1)
print("这是有参数的函数,参数为:",name)
test()
testWithParameter('LIKUNKUN')
到此,已经可以解决90%以上的装饰器的应用了。
下面是更进一步的用法:给装饰器传入一个参数
根据之前的内容,可以写出下面的代码:
3个函数,代表3个界面(仅用print表示),第一个是索引界面,不需要登录,后面是主界面和属性界面,需要登录验证:
user = 'li'
passsd = '123'
def sure(func):
def wrapper(*args,**kwargs):
username = input("用户名:").strip() #strip用来去除首位空格和换行符之类的无效符号
password = input("密码:").strip()
if user == username and passsd ==password:
return func(*args,**kwargs) #这里的return是当func表示的函数有返回值的时候,返回同样的值
else:
exit('账号密码错误')
return wrapper
#索引直接进入,后面的主页面和属性界面需要验证
def index():
print('到了索引界面')
@sure
def home():
print('到了主界面')
@sure
def power():
print('到了属性界面')
index()
home()
power()
运行结果:
现在我需要加入一个功能:进入主界面进行的是本地验证,进入属性界面进行的是网络验证,也就是需要多种验证模式。
但现在的装饰器只能支持本地验证,这时候就需要在装饰器上传入一个参数,如图:
怎么实现呢?再嵌套一层函数就好了,看下面代码,代码里注释有详解:
user = 'li'
passsd = '123'
#在套一层函数就好了
def sure(sureType): #这里传入的值就是装饰器传进来的
def outer(func): #之前的func传到这里来了
def wrapper(*args, **kwargs):
if sureType == 'local': #到这里在修改判断本地和网络的代码
username = input("用户名:").strip()
password = input("密码:").strip()
if user == username and passsd == password:
return func(*args, **kwargs)
else:
exit('账号密码错误')
elif sureType == 'web':
print('网络验证不会写,直接通过')
return func(*args, **kwargs)
else:
exit('还没开发的验证类别,直接退出')
return wrapper
return outer
#索引直接进入,后面的主页面和属性界面需要验证
def index():
print('到了索引界面')
@sure(sureType = 'local')
def home():
print('到了主界面')
return 'home'
@sure(sureType = 'web')
def power():
print('到了属性界面')
index()
home()
power()
运行结果:
至此,python之装饰器的内容,就结束了。