Python的装饰器
装饰器
初识装饰器,有一种高大上的感觉,但又不知道是啥。从字面意义上来看:装饰,就是起修饰作用,附加功能;器,就是函数;既然是函数,那就好说了,我先写这么一个函数。
装饰器的两个原则:
1.不改变原函数
2.不改变原函数的调用方式
怎么理解这两句话?不改变原函数,就是不动函数的代码,就给他添加一个功能;不改变原函数的调用方式也是一样。
装饰器 = 高阶函数+函数嵌套+闭包
这是装饰器的组成,如何一步步来实现装饰器,先看高阶函数行不行,现在我要给一个函数添加一个打印运行时间的功能,我先这样写:
import time def foo(): time.sleep(2) print('this is my house') start_time = time.time() foo() stop_time = time.time() total_time = stop_time - start_time print ("一共用了 %s 秒"%total_time)
这样已经实现了想要的功能,把下面这一段写成高阶函数,似乎可以大功告成
import time def foo(): time.sleep(2) print('this is my house') def timer(func): start_time = time.time() func() stop_time = time.time() total_time = stop_time - start_time print ("一共用了 %s 秒"%total_time) return func foo = timer(foo) foo()
先介绍一个高阶函数的定义,就是函数接收的参数是一个函数名或函数返回的参数是一个函数名,满足其中一个条件就是高阶函数。上面的运行结果是:
>>> this is my house >>> 一共用了 2.000192165374756 秒 >>> this is my house
为了不影响原函数调用,我将高阶函数也赋值给foo,但是最后运行了两次,并不满足要求,看来单用高阶函数不能满足要求,配合函数嵌套和闭包试一下。先看看函数嵌套的例子:
def father(name): print ('my father is %s' %name) def son(): print('my son is %s' %name) #当前函数没有name时,就往上一层找,这叫函数的作用域 def grandson(): name = '6788' print('我的爷爷是%s' %name) grandson() son() father('23455')
这个例子演示了函数嵌套和闭包,闭包是哪个?闭包就是里面的嵌套函数。上面用高阶函数,会多运行一次,因为在foo = timer(foo)已经运行一次了,函数嵌套好像可以处理这个问题,试一下:
import time def timmer(func): #func=test def wrapper(): # print(func) start_time=time.time() func() #就是在运行test() stop_time = time.time() print('运行时间是%s' %(stop_time-start_time)) return wrapper def test(): time.sleep(3) print('test函数运行完毕') test=timmer(test) #返回的是wrapper的地址 test() #执行的是wrapper()
看一下运行结果:
>>> test函数运行完毕
>>> 运行时间是3.0001602172851562
我去,成功了,一个简单的装饰器就这么实现了。在Python中,装饰器有自己的写法(@装饰器的函数名),就是这样的:
import time def timmer(func): #func=test def wrapper(): # print(func) start_time=time.time() func() #就是在运行test() stop_time = time.time() print('运行时间是%s' %(stop_time-start_time)) return wrapper @timmer #test=timmer(test) def test(): time.sleep(3) print('test函数运行完毕') test()
其中@timmer就是test= timmer(test)嘛,也满足装饰器的两个原则,完美!
但是上面的test函数式最简单的函数,如果函数有返回值呢,如果带参数呢?是不是还可以这么写?现在试一下带返回值的:
import time def timmer(func): #func=test def wrapper(): # print(func) start_time=time.time() func() #就是在运行test() stop_time = time.time() print('运行时间是%s' %(stop_time-start_time)) return wrapper @timmer #test=timmer(test) def test(): time.sleep(3) print('test函数运行完毕') return '返回值' print(test())
看一下结果:
>>> test函数运行完毕
>>> 运行时间是3.005080461502075
>>> None
怎么会是None?明明有返回值啊。看一下怎么运行的,print里面test()已经运行了,在wrapper()里面并没有返回值,难怪会出现None,那我加上一个就好了:
import time def timmer(func): #func=test def wrapper(): # print(func) start_time=time.time() ret = func() #就是在运行test() stop_time = time.time() print('运行时间是%s' %(stop_time-start_time)) return ret return wrapper @timmer #test=timmer(test) def test(): time.sleep(3) print('test函数运行完毕') return ('test的返回值') print(test())
这就OK了嘛。返回值搞定了,参数肯定更简单,我试一下加参数的:
import time def timmer(func): #func=test def wrapper(): # print(func) start_time=time.time() ret = func() #就是在运行test() stop_time = time.time() print('运行时间是%s' %(stop_time-start_time)) return ret return wrapper @timmer #test=timmer(test) def test(name,age): time.sleep(3) print('test函数运行完毕,名字是%s,年龄是%s' %(name,age)) return ('test的返回值') ret = test('dfg',12) print(ret)
看一下结果:
TypeError: wrapper() takes 0 positional arguments but 2 were given
报错了,这可不得报错嘛,参数都不给,现在给wrapper()和func()加上参数试一下:
import time def timmer(func): #func=test def wrapper(x,y): # print(func) start_time=time.time() ret = func(x,y) #就是在运行test() stop_time = time.time() print('运行时间是%s' %(stop_time-start_time)) return ret return wrapper @timmer #test=timmer(test) def test(name,age): time.sleep(3) print('test函数运行完毕,名字是%s,年龄是%s' %(name,age)) return ('test的返回值') ret = test('dfg',12) print(ret)
运行一下:
test函数运行完毕,名字是dfg,年龄是12 运行时间是3.0100483894348145 test的返回值
没问题,很完美。但是想一下,函数都是两个参数的吗?参数有多少种?对啊,用*args和**kwargs就搞定一切啊,走起:
import time def timmer(func): #func=test def wrapper(*args,**kwargs): # print(func) start_time=time.time() ret = func(*args,**kwargs) #就是在运行test() stop_time = time.time() print('运行时间是%s' %(stop_time-start_time)) return ret return wrapper @timmer #test=timmer(test) def test(name,age): time.sleep(3) print('test函数运行完毕,名字是%s,年龄是%s' %(name,age)) return ('test的返回值') @timmer #test=timmer(test) def test1(name,age,wifi): time.sleep(3) print('test函数运行完毕,名字是%s,年龄是%s,wifi是%s' %(name,age,wifi)) ret = test('dfg',12) print(ret) test1('dfaaa',12,'765hsdf')
这样就基本搞定了装饰器了,很简单嘛。但是有个问题,装饰器可以带参数吗?为什么不行?都是函数啊,试一下,做一个模拟淘宝登录流程,添加认证功能:
import time auth_list = [ {'username':'444','passwd':'123'}, {'username':'555','passwd':'123'}, {'username':'666','passwd':'123456'}, {'username':'777','passwd':'123456'} ] wo_dic = {'username':None,'login':False} def check(auth_type='filedb'): def check_01(func): def wrecur(*args,**kwargs): if auth_type == 'filedb': if wo_dic['username'] and wo_dic['login']: ret = func(*args, **kwargs) return ret username = input('用户名:').strip() passwd = input('密码:').strip() for i in auth_list: if username == i['username'] and passwd == i['passwd']: wo_dic['username'] = username wo_dic['login'] = True ret = func(*args, **kwargs) return ret else: print('用户名或密码错误') elif auth_type == 'otherdb': print('鬼都不知道怎么用') else: print('写的什么鬼') return wrecur return check_01 @check(auth_type='filedb') def index(): print('欢迎来到淘宝主页') @check(auth_type='otherdb') def home(name): print('%s,欢迎回家' %name) @check(auth_type='ssssss') def shopping_car(): print('购物车里有:%s,%s,%s' %('香蕉','牛奶','山羊')) index() home('产品经理') shopping_car('产品经理')
这样就实现了不同参数的装饰器对应不同的认证方式,装饰器基本就这些知识点了。