Python:装饰器
Decorator #装饰器
装饰器是什么?
顾名思义,就是用来“装饰”的:
# 语法糖 @xxx
装饰器是一个很著名的设计模式,(也和开闭原则有关)经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
#便于复习首先记录最常用的通用装饰器:
通用装饰器
def wrapper(func): def inner(*args, **kwargs): # print('check..') # 装饰内容 func(*args, **kwargs) # 执行函数 ret = fun(xxx) # 若函数带返回值,return一下 return ret return inner @wrapper def test(): print('kumata so cool !') @wrapper def test1(): print('kumata 666') return 'python' @wrapper def test2(a): print('I say kumata you say %d ' % a) test() test1() test2(666) #运行结果 kumata so cool ! kumata 666 I say kumata you say 666
一、引入装饰器
以解决一个问题来理解学习装饰器
以业务(前台)、 服务(后台)的例子:
- 业务:与客户打交道,在需要的时候,调用服务部门提供的接口
- 服务:不直接与客户打交道。在后台运行。底层、核心算法、网络
'''服务部门和业务部门的例子''' # 服务端部门写装饰器 # 验证的函数 def wrapper(fun): def check(): # 验证第一项内容 # if not xxx # return print('check1..done') # 验证第二项内容 print('check2..done') # 验证第三项内容 print('check3..done') #上面验证通过后就调用原来的函数 fun() return check # 业务部门使用接口 '''部门1、2验证,部门3、4不验证''' @wrapper def fun1(): print('hello! I\'m department 1!') @wrapper def fun2(): print('hi! I\'m department 2!') def fun3(): print('hi! I\'m department 3!') def fun4(): print('hi! I\'m department 4!') fun1() fun2() fun3() fun4() #输出结果 check1..done check2..done check3..done hello! I'm department 1! check1..done check2..done check3..done hi! I'm department 2! hi! I'm department 3! hi! I'm department 4!
二、装饰器原理
'''装饰器原理''' ''' 服务部门 ''' # 把要调用的函数作为一个参数传入wrapper()中 def wrapper(fun): def check(): # 验证 print('check done') # 调用原来的函数 fun() return check ''' 业务部门 ''' # # 不用语法糖的写法 # def fun1(): # print('---fun1---') # # def fun2(): # print('---fun2---') # f1 = wrapper(fun1) # f1() # # f2 = wrapper(fun2) # f2() # 使用语法糖 @wrapper # f1 = wrapper(fun1) f1() def fun1(): print('---fun1---') @wrapper def fun2(): print('---fun2---') fun1() fun2()
三、执行时机
了解了装饰器的原理后,对它的执行时机再写一下
#当python解释器执行到@wrapper时,就开始进行装饰了,相当于执行了代码:test = wrapper(test) def wrapper(fun): print('...装饰器开始装饰...') def inner(): print('...验证权限...') fun() print('...验证结束...') return inner @wrapper # test = wrapper(test) def test(): print('test') #不写test()来调用的输出结果 ...装饰器开始装饰... ...验证结束... test() #输出结果 ...装饰器开始装饰... ...验证权限... test ...验证结束...
四、两个装饰器执行流程和装饰结果
当有两个或两个以上装饰器装饰一个函数时
def wrapper1(fun): print('----1-1----') def inner(): print('----1-2----') return '<b>' + fun() + '</b>' return inner def wrapper2(fun): print('----2-1----') def inner(): print('----2-2----') return '<i>' + fun() + '</i>' return inner @wrapper1 @wrapper2 def test(): print('----3-1----') print('----3-2----') return 'hello python decorator' ret = test() print(ret) #输出结果 ----2-1---- ----1-1---- ----1-2---- ----2-2---- ----3-1---- ----3-2---- <b><i>hello python decorator</i></b>
分析如下:
- 通过上面装饰时机的介绍,我们可以知道,在执行到 @wrapper1 的时候,需要对下面的函数进行装饰,此时解释器继续往下走,发现并不是一个函数名,而又是一个装饰器,
- 这时候,@wrapper1 装饰器暂停执行,而接着执行接下来的装饰器 @wrapper2 ,接着把test函数名传入装饰器函数,从而打印 ’2-1’ ,
- 在 wrapper2 装饰完后,此时的 test 指向 wrapper2 的 inner 函数地址,这时候又返回来执行 @wrapper1,接着把新 test 传入 wrapper2 装饰器函数中,因此打印了’1-1’。
- 在调用 tes t函数的时候,根据上述分析,此时 test 指向 wrapper1.inner函数,因此会先打印‘1-2‘,
- 接下来,在调用fun()的时候,其实是调用的wrapper2.inner()函数,所以打印 ‘2-2‘ ,
- 在wrapper2.inner中,调用的fun其实才是我们最原声的test函数,所以打印原test函数中的‘3-1‘,‘3-2‘,
- 所以在一层层调完之后,打印的结果为<b><i>hello python decorator</i></b> 。
五、对有参函数进行装饰
以上为无参数函数使用,在使用中,有的函数可能会带有参数,那么这种如何处理呢?
def wrapper(fun): """ 如果原函数有参数,那闭包函数必须保持参数个数一致,并且将参数传递给原方法 """ def inner(name): """ 如果被装饰的函数有行参,那么闭包函数必须有参数 :param name: :return: """ print('w\'s inner is called') fun(name) return inner @wrapper def hello(name): print('hello ' + name) hello('dz') #运行结果 w's inner is called hello dz
多个或者不定长参数
def wrapper(fun): def inner(*args,**kwargs): print('lets go') fun(*args,**kwargs) return inner @wrapper def fun1(a): print('hello' + a) fun1('kumata') @wrapper def fun2(a,b): print('%d + %d = %d' %(a,b,a+b)) fun2(3,8) #输出结果 lets go hellokumata lets go 3 + 8 = 11
六、对带返回值的函数进行装饰
def wrapper(fun): def wrapper_in(): print('check') return fun() return wrapper_in @wrapper def fun1(): print('1111') return '2222' fun1() # 输出fun1() print('------') print(fun1()) #输出fun1()返回值 # 输出结果 check 1111 ------ check 1111 2222
七、带参数的装饰器
写完对带参数的函数和有返回值的函数进行装饰,还有一个带参数的装饰器
''' 带有参数的装饰器能够起到在运行时,有不同的功能 先执行wrapper('boss'),返回wrapper_fun函数的引用 @wrapper_fun 使用@wrapper_fun对fun进行装饰 嗯..套多了一层的意思啦 ''' def wrapper(param): # param = 'boss' def wrapper_fun(fun): # fun = fun1/fun2 def inner(): print(' %s say:' % param) fun() return inner return wrapper_fun @wrapper('boss') # fun1 = wrapper('boss')(fun1) def fun1(): print('1!!') @wrapper('boss') # fun2 = wrapper('boss')(fun2) def fun2(): print('2!!') fun1() fun2() #输出结果 boss say: 1!! boss say: 2!!
八、类装饰器
装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。
在python中,一般callable对象都是函数,但是也有例外。比如只要某个对象重写了call方法,那么这个对象就是callable的。
#当创建一个对象后,直接去执行这个对象,那么是会抛出异常的,因为他不是callable,无法直接执行,但进行修改后,就可以直接执行调用了 class Test(object): def __call__(self, *args, **kwargs): print('call called') t = Test() print(t()) #运行结果 call called #下面引入正题,看一下如何用类装饰函数 class Test(object): def __init__(self, func): print('test init') print('func name is %s ' % func.__name__) self.__func = func def __call__(self, *args, **kwargs): print('装饰器中的功能') self.__func() @Test def test(): print('this is test func') test() #运行结果 test init func name is test 装饰器中的功能 this is test func #和之前的原理一样,当python解释器执行到到@Test时,会把当前test函数作为参数传入Test对象,调用init方法,同时将test函数指向创建的Test对象,那么在接下来执行test()的时候,其实就是直接对创建的对象进行调用,执行其call方法。
学习参考资料:
Github地址:https://github.com/kumataahh