装饰器
装饰器:
装饰器就是闭包函数的一种应用,为什么这么说哪?原因要从其应用说起,当我们要给一个函数添加新的属性和功能,但是又不能修改此函数本身,此时我们就要用到装饰器。装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。本质上就是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象,它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用
为了当每一次调用还是引用原来的变量名,此时就要保留原先的变量名,而引用一个内嵌函数。一般方法:
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') # foo = logger(True)(f) ==> show_time(f) ==> inner 注意这就是装饰器的横向的特性
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) # 引入一个参数来判断,第一步从这执行,类似于 func = outer(False)(func)
def func():
print(111)
func()
from functools import wraps def log(flag): def warpper(f): # 接受被装饰的函数 if flag: @wraps(f) def inner(*args,**kwargs): #接受参数 """ 这是inner的注释 :param args: :param kwargs: :return: """ print('这是一个装饰器') ret = f(*args,**kwargs) return ret return inner else: return f return warpper print(log(True).__name__) #warpper # @log(True) # func = log(参数)(函数名)(函数参数) def func(): """ 这是func函数 :return: """ print(1) # # func() log(True)(func)() # 这种方式如装饰器一样 print(func.__doc__) 运行结果 warpper 这是一个装饰器 1 这是func函数 :return:
多个装饰器作用于同一个函数(纵向衍生):
def wrapper1(func): def inner1(): print('wrapper1 ,before func') func() print('wrapper1 ,after func') return inner1 def wrapper2(func): def inner2(): print('wrapper2 ,before func') func() print('wrapper2 ,after func') return inner2 @wrapper2 #f = wrapper2(wrapper1(f)) 这里我们带入后的结果为 f = wrapper2(inner1)=inner2 此时f已存在wrapper1中,使其为闭包暂存,
@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))) 类似一个凹槽往中间走,再出来 V
def log(func): def _wrapper2(*args, **kwargs): print ("call %s " % func.__name__) return func(*args, **kwargs) return _wrapper2 def check_login(func): def _wrapper1(name, password): if name != "wang" or password != "123456": print ("login failed") return "username or password error" print('im coming') return func(name, password) return _wrapper1 @log @check_login # login == check_login(login) ++>log(_wrapper1) ==>_warpper2()此时装饰器会在、 # 最外层开始进入开始调用,然后打印'call _wrapper1',此时开始找func(此时就是_wrapper1),但是func是在第一层 # _wrapper1传入的,所以它会回到_wrapper1执行,最后回到_wrapper2 退出 def login(name, password): print ("login success.") print ("do something.") return 'ok' print(login('wang','123456')) 结果 #call _wrapper1 #im coming #login success. #do something. #ok
总结:
一、装饰器:装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。强调装饰器的原则: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
1、多层装饰器使用范围 def decorator_a(func): print ('Get in decorator_a') def inner_a(*args, **kwargs): print ('Get in inner_a') res = func(*args, **kwargs) return res return inner_a def decorator_b(func): print ('Get in decorator_b') def inner_b(*args, **kwargs): print ('Get in inner_b') res = func(*args, **kwargs) return res return inner_b # 当有多个装饰器时,从下到上调用装饰器 @decorator_a @decorator_b def f(x): print ('Get in f') return x * 2 f(2) # Get in decorator_b # Get in decorator_a # Get in inner_a # Get in inner_b # Get in f 2、多层嵌套的应用 def makebold(fn): def wrapper2(): return '<b>' +fn()+'<b>' return wrapper2 def makeitalic(fn): def wrapper1(): return '<p>' +fn()+'<p>' return wrapper1 @makebold @makeitalic #hello()==wrapper1(wrapper2()) def hello(): return 'hello alice' print(hello()) #<b><p>hello alice<p><b> 3、类使用装饰器。相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。 import time class Foo(object): def __init__(self, func): self._func = func def __call__(self): start_time=time.time() self._func() end_time=time.time() print('spend %s'%(end_time-start_time)) @Foo #bar=Foo(bar) def bar(): print ('bar') time.sleep(2) bar() #bar=Foo(bar)()>>>>>>>没有嵌套关系了,直接active Foo的 __call__方法 4、使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子 原例子: def foo(): print("hello foo") print(foo.__name__) ##################### def logged(func): def wrapper(*args, **kwargs): print (func.__name__ + " was called") return func(*args, **kwargs) return wrapper @logged def cal(x): return x + x * x print(cal.__name__) ######## # foo # wrapper 使用后的例子: from functools import wraps def logged(func): @wraps(func) def wrapper(*args, **kwargs): print (func.__name__ + " was called") return func(*args, **kwargs) return wrapper @logged def cal(x): return x + x * x print(cal.__name__) #cal
习题:
四:编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码 注意:从文件中读出字符串形式的字典,可以用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']: # 条件判断,如果登陆用户存在,且登陆则返回函数func 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 in dic: # 这里不太懂,因为试了很多次都是TypeError: string indices must be integers 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()
总结装饰器的默认格式:
def wrapper(fn): # return fn # 定义嵌套函数, def inner(*args,**kwargs): # 对原始函数的调用 ret = fn(*args,**kwargs) # 对原始函数的返回值进行处理 if ret < 0: return -ret else : return ret # 把嵌套函数返回 return inner @wrapper def func(a,b): return a - b print(func(5,3)) print(func(5,30)) # 定义装饰器 def w(min_value,max_value): def wrapper(fn): # return fn # 定义嵌套函数, def inner(*args,**kwargs): # 对原始函数的调用 ret = fn(*args,**kwargs) # 对原始函数的返回值进行处理 if ret < min_value: return min_value elif ret > max_value: return max_value # 把嵌套函数返回 return inner return wrapper @w(-5,5) def fun(a,b): return a + b
进阶横向衍生装饰器
def Before(request): print 'before' def After(request): print 'after' def Filter(before_func,after_func): def outer(main_func): def wrapper(request): before_result = before_func(request) if(before_result != None): return before_result; main_result = main_func(request) if(main_result != None): return main_result; after_result = after_func(request) if(after_result != None): return after_result; return wrapper return outer @Filter(Before, After) def Index(request): print 'index' Index('example') 先上结果: before index after