装饰器:
装饰器就是闭包函数的一种应用,为什么这么说哪?原因要从其应用说起,当我们要给一个函数添加新的属性和功能,但是又不能修改此函数本身,此时我们就要用到装饰器,
为了当每一次调用还是引用原来的变量名,此时就要保留原先的变量名,而引用一个内嵌函数。一般方法:
import time
def foo(): print('This is foo....') def show_time(f): start = time.time() f() time.sleep(2) end = time.time() print('花费%s秒'%(end-start)) #This is foo.... 花费2.000157356262207秒 show_time(foo) #此时我们是通过show_time来调用foo函数,但是在实际的生产中,一般不会改变调用方法,需要改进
二次改进:(通过闭包) import time def foo(): print('This is foo....') def show_time(f): def inner(): start = time.time() f() time.sleep(2) end = time.time() print('花费%s秒'%(end-start)) #我们通过内嵌函数来通过改变内嵌函数名来避开原有的函数名 return inner foo = show_time(foo) foo() #此时就没有改变原有的调用方式 ,所以说装饰器就是闭包的一种应用
三次改进:(通过Python装饰器) import time def show_time(f): start = time.time() f() time.sleep(2) end = time.time() print('花费%s秒'%(end-start)) @show_time # foo = show_time(foo) 这一步就目的就是让在不改变最初变量名的前提下修改函数 def foo(): print('This is foo....') # 从整个代码来看,其实就是将def foo 及函数题作为参数带入到show_time函数中
总结以上代码: 当再一次调用foo函数时,其实是执行得蓝色框里面的代码,函数foo被当作参数进去
功能函数加参数:
一般情况下,功能函数都是含有参数的,所以在装饰器中的内嵌函数的参数一定要于功能函数的参数一致才行,当然也可以使用到万能参数格式。
def timer(func): def inner(a): start = time.time() func(a) print(time.time() - start) return inner @timer # func1 = timer(func1) 这里并没有传入参数 def func1(a): print(a) func1(1)
功能函数多参数:
import time def timer(func): def inner(*args,**kwargs): # 内嵌函数是万能参数 start = time.time() re = func(*args,**kwargs) # 注意这里的re 是接收的func1的返回值,None print(time.time() - start) return re # 返回的一个over,在print作用下打印 return inner @timer #==> func1 = timer(func1) def func1(a,b): print('in func1') time.sleep(1) func1(2,4) @timer #==> func2 = timer(func2) def func2(a): print('in func2 and get a:%s'%(a)) time.sleep(1) return 'over' func1('aaaaaa','bbbbbb') print(func2('aaaaaa')) # in func2 and get a:aaaaaa 1.007641315460205 over func2(2)
装饰器带参数:
# 写一个函数,使其在想要添加的时候可以添加日志,且不能修改原功能函数 def lo(): print('logger is working!') def logger(flag): # 通过添加logger函数来使在想要添加日记记录lo函数时,添加 def show_time(f): def inner(*x,**y): start = time.time() f(*x,**y) end = time.time() print('spend %s'%(end-start)) if flag == 'True': lo() return 'over' return inner return show_time @logger('True') # 因为添加了return 所以此句相当于@show_time 从而相当于foo = show_time(foo) 最先执行logger函数,参入一个参数,以作为判断标准 def foo(a,b): print(a+b) time.sleep(1) print(foo(2,4))
假如你有成千上万个函数使用了一个装饰器,现在你想把这些装饰器都取消掉,你要怎么做?
一个一个的取消掉? 没日没夜忙活3天。。。 过两天你领导想通了,再让你加上。。。
def outer(flag): def timer(func): def inner(*args,**kwargs): if flag: print('''执行函数之前要做的''') re = func(*args,**kwargs) if flag: print('''执行函数之后要做的''') return re return inner return timer @outer(False) # 引入一个参数来判断,第一步从这执行 def func(): print(111) func()
多个装饰器作用于同一个函数:
def wrapper1(func): def inner(): print('wrapper1 ,before func') func() print('wrapper1 ,after func') return inner def wrapper2(func): def inner(): print('wrapper2 ,before func') func() print('wrapper2 ,after func') return inner @wrapper2 # f = wrapper2(wrapper1(f)) @wrapper1 def f(): print('in f') f() 运行结果: wrapper2 ,before func wrapper1 ,before func in f wrapper1 ,after func wrapper2 ,after func 例2: @deco1 @deco2 @deco3 def foo(): pass # foo=deco1(deco2(deco3(foo)))
总结:
一、装饰器:装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。强调装饰器的原则:1 不修改被装饰对象的源代码 2 不修改被装饰对象的调用方式,装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能。
二、装饰器的开放封闭原则:
1.对扩展是开放的
为什么要对扩展开放呢?
我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。
2.对修改是封闭的
为什么要对修改封闭呢?
就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。装饰器完美的遵循了这个开放封闭原则。
三、装饰器的主要功能和装饰器的固定结构
装饰器的主要功能:在不改变函数调用方式的基础上在函数的前、后添加功能
装饰器固定格式:
def timer(func): def inner(*args,**kwargs): '''执行函数之前要做的''' re = func(*args,**kwargs) '''执行函数之后要做的''' return re return inner 含有warps格式: from functools import wraps def deco(func): @wraps(func) #加在最内层函数正上方 def wrapper(*args,**kwargs): return func(*args,**kwargs) return wrapper @deco def index(): '''哈哈哈哈''' print('from index') print(index.__doc__)
四、补充:
查看函数的一些信息的方法在此处都会失效
def index(): '''这是一个主页信息''' print('from index') print(index.__doc__) #查看函数注释的方法 print(index.__name__) #查看函数名的方法 #结果:这是一个主页信息 index
习题:
四:编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码 注意:从文件中读出字符串形式的字典,可以用eval('{"name":"egon","password":"123"}')转成字典格式 db='db.txt' login_status={'user':None,'status':False} def auth(auth_type='file'): def auth2(func): def wrapper(*args,**kwargs): if login_status['user'] and login_status['status']: return func(*args,**kwargs) if auth_type == 'file': with open(db,encoding='utf-8') as f: dic=eval(f.read()) name=input('username: ').strip() password=input('password: ').strip() if name in dic and password == dic[name]: login_status['user']=name login_status['status']=True res=func(*args,**kwargs) return res else: print('username or password error') elif auth_type == 'sql': pass else: pass return wrapper return auth2 @auth() def index(): print('index') @auth(auth_type='file') def home(name): print('welcome %s to home' %name) # index() # home('egon') 五:编写装饰器,为多个函数加上认证功能,要求登录成功一次,在超时时间内无需重复登录,超过了超时时间,则必须重新登录 import time,random user={'user':None,'login_time':None,'timeout':0.000003,} def timmer(func): def wrapper(*args,**kwargs): s1=time.time() res=func(*args,**kwargs) s2=time.time() print('%s' %(s2-s1)) return res return wrapper def auth(func): def wrapper(*args,**kwargs): if user['user']: timeout=time.time()-user['login_time'] if timeout < user['timeout']: return func(*args,**kwargs) name=input('name>>: ').strip() password=input('password>>: ').strip() if name == 'egon' and password == '123': user['user']=name user['login_time']=time.time() res=func(*args,**kwargs) return res return wrapper @auth def index(): time.sleep(random.randrange(3)) print('welcome to index') @auth def home(name): time.sleep(random.randrange(3)) print('welcome %s to home ' %name) index() home('egon') 七:为题目五编写装饰器,实现缓存网页内容的功能: 具体:实现下载的页面存放于文件中,如果文件内有值(文件大小不为0),就优先从文件中读取网页内容,否则,就去下载,然后存到文件中 扩展功能:用户可以选择缓存介质/缓存引擎,针对不同的url,缓存到不同的文件中 import requests import os cache_file='cache.txt' def make_cache(func): def wrapper(*args,**kwargs): if not os.path.exists(cache_file): with open(cache_file,'w'):pass if os.path.getsize(cache_file): with open(cache_file,'r',encoding='utf-8') as f: res=f.read() else: res=func(*args,**kwargs) with open(cache_file,'w',encoding='utf-8') as f: f.write(res) return res return wrapper @make_cache def get(url): return requests.get(url).text # res=get('https://www.python.org') # print(res) #题目七:扩展版本 import requests,os,hashlib engine_settings={ 'file':{'dirname':'./db'}, 'mysql':{ 'host':'127.0.0.1', 'port':3306, 'user':'root', 'password':'123'}, 'redis':{ 'host':'127.0.0.1', 'port':6379, 'user':'root', 'password':'123'}, } def make_cache(engine='file'): if engine not in engine_settings: raise TypeError('egine not valid') def deco(func): def wrapper(url): if engine == 'file': m=hashlib.md5(url.encode('utf-8')) cache_filename=m.hexdigest() cache_filepath=r'%s/%s' %(engine_settings['file']['dirname'],cache_filename) if os.path.exists(cache_filepath) and os.path.getsize(cache_filepath): return open(cache_filepath,encoding='utf-8').read() res=func(url) with open(cache_filepath,'w',encoding='utf-8') as f: f.write(res) return res elif engine == 'mysql': pass elif engine == 'redis': pass else: pass return wrapper return deco @make_cache(engine='file') def get(url): return requests.get(url).text # print(get('https://www.python.org')) print(get('https://www.baidu.com')) 八:还记得我们用函数对象的概念,制作一个函数字典的操作吗,来来来,我们有更高大上的做法,在文件开头声明一个空字典,然后在每个函数前加上装饰器,完成自动添加到字典的操作 route_dic={} def make_route(name): def deco(func): route_dic[name]=func return deco @make_route('select') def func1(): print('select') @make_route('insert') def func2(): print('insert') @make_route('update') def func3(): print('update') @make_route('delete') def func4(): print('delete') print(route_dic) 九 编写日志装饰器,实现功能如:一旦函数f1执行,则将消息2017-07-21 11:12:11 f1 run写入到日志文件中,日志文件路径可以指定 注意:时间格式的获取 import time time.strftime('%Y-%m-%d %X') import time import os def logger(logfile): def deco(func): if not os.path.exists(logfile): with open(logfile,'w'):pass def wrapper(*args,**kwargs): res=func(*args,**kwargs) with open(logfile,'a',encoding='utf-8') as f: f.write('%s %s run\n' %(time.strftime('%Y-%m-%d %X'),func.__name__)) return res return wrapper return deco @logger(logfile='aaaaaaaaaaaaaaaaaaaaa.log') def index(): print('index') index()