一,什么是装饰器
装饰器:本质就是函数,功能是为其他函数添加附加功能
原则
1,不修改被修饰函数的源代码
2,不修改被修饰函数的调用方式
举例说明:有一个求和函数要求就算出函数的运行时间 正常代码应该这样day20-1.py
import time def cal(l): start_time=time.time() res=0 for i in l: res+=i stop_time=time.time() print('函数的运行时间是%s'%(stop_time-start_time)) return res res = cal(range(1,1001)) print(res) 函数的运行时间是0.0 500500
假如有多个函数都需要这种统计时间的功能,遵循开放封闭的原则不能修改函数源代码
装饰器=高阶函数+函数嵌套+闭包 (高阶函数定义 函数接受参数是一个函数名或者返回是一个函数名)
这时候就可以使用装饰器day20-2.py
import time def timmer(func): def wapper(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) stop_time=time.time() print('函数的运行时间是%s'%(stop_time-start_time)) return res return wapper @timmer def cal(l): res=0 for i in l: time.sleep(0.1) res+=i return res res = cal(range(10)) print(res) 函数的运行时间是1.0049562454223633 45
需要了解装饰器先看几个高阶函数列子,函数调用接受参数为一个函数名day20-4.py
该列子同样是增加一个统计函数运行时间的功能,修改了函数test增加了统计时间的功能,没有修改原函数foo的源代码,但是修改了调用函数的方式需要使用test(函数名)来调用,不符合装饰器规则
import time def foo(): print('Hello World') def test(func): #打印传递的函数的地址 print(func) start_time=time.time() #在内部运行函数 func() stop_time=time.time() #统计传递函数运行时间 print('函数运行时间是%s'%(stop_time-start_time)) test(foo)
<function foo at 0x000001C73CB79048>
Hello World
函数运行时间是0.0
优化以上代码day20-5.py
首先执行timer(foo)把函数名作为参数返回值赋值给foo 由于在函数timer里面有运行一遍函数foo然后又调用了一遍foo函数所以打印了两个from foo 不符合要求
所以高阶函数不能满足装饰器的要求
import time def foo(): time.sleep(0.2) print('from foo') def timer(func): start_time=time.time() func() stop_time=time.time() print('函数运行时间是%s' % (stop_time - start_time)) return func foo=timer(foo) foo() from foo 函数运行时间是0.2000875473022461 from foo
二,函数的闭包
简单说,闭包就是根据不同的配置信息得到不同的结果
专业的解释:闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
闭包装饰器的基本实现 day20-7.py
同样实现的功能是增加函数运行时间的统计
1,把函数名作为实参传递给装饰器函数timmer并且把返回值函数wrapper的内存地址返回给变量test
2,通过test()执行函数相当于执行了函数wrapper同时在里面执行了传递的函数test
3,在执行test()函数的前后加了代码实现统计运行时间的功能
4,函数的源代码及调用方式没有改变
import time def timmer(func): def wrapper(): start_time=time.time() func() stop_time=time.time() print('函数运行时间是%s'%(stop_time-start_time)) return wrapper def test(): time.sleep(2) print('test函数运行完毕') test=timmer(test) test() test函数运行完毕 函数运行时间是2.0007388591766357
可以使用python的@语法简化代码 day20-8.py 这就实现了装饰器的功能
import time def timmer(func): def wrapper(): start_time=time.time() func() stop_time=time.time() print('函数运行时间是%s'%(stop_time-start_time)) return wrapper #@加装饰器函数名相当于执行了test=timmer(test) @timmer def test(): time.sleep(2) print('test函数运行完毕') test() test函数运行完毕 函数运行时间是2.0001330375671387
以上装饰器装饰的函数test是没有返回值的这样运行是没有问题的 假如函数test有返回值需要在装饰器里面的函数wrapper里面定义返回值,否则返回为空
day20-9.py
import time def timmer(func): def wrapper(): start_time=time.time() #本次就相当于运行test函数 res = func() stop_time=time.time() print('函数运行时间是%s'%(stop_time-start_time)) return res return wrapper #@加装饰器函数名相当于执行了test=timmer(test) @timmer def test(): time.sleep(2) print('test函数运行完毕') return '这是函数test的返回值' #因为加了装饰器相当于已经执行了test=timmer(test)并且把函数wrapper内存返回 #给了变量test这里test加()相当于执行了函数wrapper 所以加入需要装饰的函数有返回值 #需要在函数wrapper里面定义返回值 res = test() print(res) test函数运行完毕 函数运行时间是2.000873327255249 这是函数test的返回值
函数闭包加上参数
假如需要装饰的函数有参数怎么办,可以在装饰器里面加万能接收参数可以接收任何参数
day20-10.py
import time def timmer(func): #*args,**kwargs参数代表接收任何参数其中*args接收一个元祖 #**kwargs接收字典 def wrapper(*args,**kwargs): start_time=time.time() #本次就相当于运行test函数 res = func(*args,**kwargs) stop_time=time.time() print('函数运行时间是%s'%(stop_time-start_time)) return res return wrapper #@加装饰器函数名相当于执行了test=timmer(test) @timmer def test(name,age): time.sleep(2) print('test函数运行完毕,名字是%s年龄是%s'%(name,age)) return '这是函数test的返回值' #因为加了装饰器相当于已经执行了test=timmer(test)并且把函数wrapper内存返回 #给了变量test这里test加()相当于执行了函数wrapper 所以加入需要装饰的函数有返回值 #需要在函数wrapper里面定义返回值 res = test('zhangsan',18) print(res) test函数运行完毕,名字是zhangsan年龄是18 函数运行时间是2.0006587505340576 这是函数test的返回值
函数闭包为函数加上认证功能day20-12.py
def auth_func(func): def wrapper(*args,**kwargs): username=input('用户名:').strip() passwd=input('密码:').strip() if username == 'sb' and passwd=='123': res = func(*args,**kwargs) return res else: print('用户名或密码错误') return wrapper @auth_func def index(): print('欢迎来到主页') @auth_func def home(name): print('欢迎回家%s'%name) @auth_func def shopping_car(name): print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','美女','娃娃')) def order(): pass index() home('产品经理') shopping_car('产品经理')
最后三条语句执行对应函数,因为加了装饰器必须要先通过输入用户名和密码登录认证才能打开对应的页面
函数闭包模拟session
以上虽然实现了登录验证功能,但是每执行一个页面都需要登录一次不符合实际需要,实际只需要登录一次即可
day20-13.py
#定义一个字典包含用户名初始值为空登录状态为False user_dic={'username':None,'login':False} def auth_func(func): def wrapper(*args,**kwargs): #如果已经登录了username有值并且login状态为True #则调用函数为直接调用并且返回,因为包含return就不会 #执行以下是输入了 if user_dic['username'] and user_dic['login']: res = func(*args,**kwargs) return res username=input('用户名:').strip() passwd=input('密码:').strip() #如果输入正确的用户名和密码则代表登录成功 #把输入的用户名赋值给字典的key username #把登录状态login设置为True if username == 'sb' and passwd=='123': user_dic['username']=username user_dic['login']=True res = func(*args,**kwargs) return res else: print('用户名或密码错误') return wrapper @auth_func def index(): print('欢迎来到主页') @auth_func def home(name): print('欢迎回家%s'%name) @auth_func def shopping_car(name): print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','美女','娃娃')) def order(): pass index() home('产品经理') shopping_car('产品经理') 用户名:sb 密码:123 欢迎来到主页 欢迎回家产品经理 产品经理的购物车里有[奶茶,美女,娃娃]
只需要在执行第一个index的时候需要输入用户名和密码,再执行home和shopping_car的时候就不需要再次输入了
以上实现了登录验证并且保存session但是把用户名和密码固定了,实际情况应该是有一个列表保留用户名和密码,等待用户输入然后去列表里面找是否对应
day20-14.py
#模拟定义一个列表,列表包含多个字典,每一个字典保存用户名和密码信息 user_list =[ {'name':'zhangsan','passwd':'123'}, {'name':'lisi','passwd':'123'}, {'name':'wangwu','passwd':'123'} ] #定义一个字典包含用户名初始值为空登录状态为False current_dic={'username':None,'login':False} def auth_func(func): def wrapper(*args,**kwargs): #如果已经登录了username有值并且login状态为True #则调用函数为直接调用并且返回,因为包含return就不会 #执行以下的输入了 if current_dic['username'] and current_dic['login']: res = func(*args,**kwargs) return res username=input('用户名:').strip() passwd=input('密码:').strip() for index,user_dir in enumerate(user_list): if username == user_dir['name'] and passwd == user_dir['passwd']: current_dic['username']=username current_dic['login']=True res = func(*args,**kwargs) return res else: print('用户名或密码错误') return wrapper @auth_func def index(): print('欢迎来到主页') @auth_func def home(name): print('欢迎回家%s'%name) @auth_func def shopping_car(name): print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','美女','娃娃')) def order(): pass index() home('产品经理') shopping_car('产品经理')
带参数验证功能的装饰器
假如验证的方式不止一种,有可能是上面的列表的方式也有可能是数据库或者一个文件保存用户信息则需要使用带参数功能的装饰器
day20-15.py
#模拟定义一个列表,列表包含多个字典,每一个字典保存用户名和密码信息 user_list =[ {'name':'zhangsan','passwd':'123'}, {'name':'lisi','passwd':'123'}, {'name':'wangwu','passwd':'123'} ] #定义一个字典包含用户名初始值为空登录状态为False current_dic={'username':None,'login':False} def auth(auth_type='filedb'): def auth_func(func): def wrapper(*args,**kwargs): print('认证类型是',auth_type) #如果已经登录了username有值并且login状态为True #则调用函数为直接调用并且返回,因为包含return就不会 #执行以下的输入了 if auth_type == 'filedb': if current_dic['username'] and current_dic['login']: res = func(*args,**kwargs) return res username=input('用户名:').strip() passwd=input('密码:').strip() for user_dir in user_list: if username == user_dir['name'] and passwd == user_dir['passwd']: current_dic['username']=username current_dic['login']=True res = func(*args,**kwargs) return res else: print('用户名或密码错误') elif auth_type == 'ldap': print('不会玩ldap认证方式') res = func(*args, **kwargs) return res else: print('不知道你用的什么认证方式') res = func(*args, **kwargs) return res return wrapper return auth_func @auth(auth_type='filedb') def index(): print('欢迎来到主页') @auth(auth_type='ldap') def home(name): print('欢迎回家%s'%name) @auth(auth_type='sss') def shopping_car(name): print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','美女','娃娃')) index() home('产品经理') shopping_car('产品经理') 认证类型是 filedb 用户名:zhangsan 密码:123 欢迎来到主页 认证类型是 ldap 不会玩ldap认证方式 欢迎回家产品经理 认证类型是 sss 不知道你用的什么认证方式 产品经理的购物车里有[奶茶,美女,娃娃]
这里定义更深一层的装饰器函数,不同的页面采用不同的认证方式(默认认证方式是filedb)只是使用print模拟输出,并没有实际实现对应的功能
PS:使用不带参数的装饰器可以满足大部分装饰器需求,带参数的装饰器较少使用。