1、首先,有个原来写好的函数,完成一定的功能,比如下面的,就打印一句话(某程序被调用)。简单点,容易帮我们想清楚程序是怎么执行的。
1 ‘‘‘ 2 原函数 3 ‘‘‘ 4 def fun1(): 5 print("fun1 is called.") 6 7 fun1()
结果:
2、然后,有个需求,要在原来的函数上加个功能,我们简单点,就再打印一句话吧(某新功能加入)。
2.1 不懂装饰器,就直白点吧,加一个fun2
1 ‘‘‘ 2 在原来的函数上加个功能 3 再打印一句话吧(某新功能加入)。 4 ‘‘‘ 5 def fun2(): 6 print("new fun is called") 7 8 ‘‘‘ 9 原函数 10 打印一句话(某程序被调用), 11 ‘‘‘ 12 def fun1(): 13 fun2() 14 print("fun1 is called.") 15 16 fun1()
结果:
这样子,结果是我们想要的,先执行新功能的程序,再执行原功能的程序,但是有个问题,原来的程序中加入了新的代码,示例中的13行。
写程序有个原则,叫封闭,就是原来跑得好好的代码不要再去改它,免得改来改去得再出问题。所以,我们得改。
2.2 不修改源代码,我们把原函数名字作为参数传给新功能的函数,先跑新功能,完了再掉用作为参数传进去的原功能
1 ‘‘‘ 2 在原来的函数上加个功能 3 再打印一句话吧(某新功能加入)。 4 ‘‘‘ 5 def fun2(funname): 6 print("new fun is called") 7 funname() 8 9 ‘‘‘ 10 原函数 11 打印一句话(某程序被调用), 12 ‘‘‘ 13 def fun1(): 14 print("fun1 is called.") 15 16 fun2(fun1)
结果:
也是我们要的结果。然后新问题来了,这种方式,可不是原来直接调用fun1(),那么程序中所有用到fun1()的地方都得改成fun2(fun1),如果需要实现对就功能进行加强的地方很多,可以想象,你得找到所有的地方进行修改,程序大了,可不能这么干,万一漏了呢?我们还得改。
2.3 不改源代码,不改调用方式
把原来的程序在新程序中返回,然后再赋值给fun1,这样执行会是什么结果呢?
1 ‘‘‘ 2 在原来的函数上加个功能 3 再打印一句话吧(某新功能加入)。 4 ‘‘‘ 5 def fun2(funname): 6 print("new fun is called") 7 return funname 8 9 ‘‘‘ 10 原函数 11 打印一句话(某程序被调用), 12 ‘‘‘ 13 def fun1(): 14 print("fun1 is called.") 15 16 fun1=fun2(fun1) 17 18 fun1()
结果:
在这个程序里,调用fun1()可以获得和原来一样的结果,但是,这里有个问题,“new fun is called”是在16行的时候,运行fun2时出来的,“fun1 is called”是在18行运行fun1时出来的,也就是说这个程序实际上并没有将fun1和fun2关联起来,它并没有在fun1()调用的时候同时实现新旧两个功能,它和以下代码是一样的
1 def fun2(): 2 print("new fun is called") 3 return 4 def fun1(): 5 print("fun1 is called.") 6 fun2() 7 fun1()
这就没有装饰的意思了
2.4 不改源代码,不改调用方式,一次调用执行实现新旧两个功能
1 ‘‘‘ 2 在原来的函数上加个功能 3 再打印一句话吧(某新功能加入)。 4 ‘‘‘ 5 def fun2(funname): 6 def inner(): 7 print("new fun is called") 8 funname() 9 return inner 10 11 ‘‘‘ 12 原函数 13 打印一句话(某程序被调用), 14 ‘‘‘ 15 def fun1(): 16 print("fun1 is called.") 17 18 19 fun1=fun2(fun1) 20 21 fun1()
在这个代码里,fun1作为参数传给fun2,在fun2中定义了一个函数,在这个嵌套定义的函数中实现了新功能,那么fun2干嘛呢?它只是将这个新功能的地址作为返回值返回。我们拿到这个返回值,重新赋值给fun1,那么再调用fun1的时候,实际上是调用的fun2中定义的新功能函数。
结果:
我们可以通过debug运行,给每行打上断点,就能知道最终的结果是在运行到21行的时候一起出来的。
2.5 在以上基础上,可以看到旧功能是在fun1中实现的,新功能是在fun2中实现的,调用的还是fun1,多了一句话,即19行,传参+复制,会不会搞不清楚,于是,简写一下
1 ‘‘‘ 2 在原来的函数上加个功能 3 再打印一句话吧(某新功能加入)。 4 ‘‘‘ 5 def fun2(funname): 6 def inner(): 7 print("new fun is called") 8 funname() 9 return inner 10 11 ‘‘‘ 12 原函数 13 打印一句话(某程序被调用), 14 ‘‘‘ 15 @fun2 16 def fun1(): 17 print("fun1 is called.") 18 19 fun1()
将@fun2写在fun1之前,就表示fun2写了新功能,它是在fun1的基础上来的,把fun1妆点了一下门面,有点加强了--装饰器这个名字很贴切的。
2.6 函数的定义是为了重复调用,可以少写代码,现在我们有fun2(新功能),可以想象,可能有很多其他的fun**(旧功能)需要妆点,如果原来的fun**本身带参数,那么就会报错。
1 ‘‘‘ 2 在原来的函数上加个功能 3 再打印一句话吧(某新功能加入)。 4 ‘‘‘ 5 def fun2(funname): 6 def inner(): 7 print("new fun is called") 8 funname() 9 return inner 10 11 ‘‘‘ 12 原函数 13 打印一句话(某程序被调用), 14 ‘‘‘ 15 @fun2 16 def fun1(): 17 print("fun1 is called.") 18 19 @fun2 20 def fun3(arg1): 21 print("fun3 welcome: %s "%arg1) 22 23 fun1() 24 fun3("susen")
结果:
所以,我们需要用非固定参数来解决这个问题。在装饰器的inner函数用非固定参数*args,**kwargs,这样,不管原来的函数有多少参数,都可以调用了。
1 ‘‘‘ 2 在原来的函数上加个功能 3 再打印一句话吧(某新功能加入)。 4 ‘‘‘ 5 def fun2(funname): 6 def inner(*args,**kwargs): 7 print("new fun is called") 8 funname(*args,**kwargs) 9 return inner 10 11 ‘‘‘ 12 原函数 13 打印一句话(某程序被调用), 14 ‘‘‘ 15 @fun2 16 def fun1(): 17 print("fun1 is called.") 18 19 @fun2 20 def fun3(arg1): 21 print("fun3 welcome: %s "%arg1) 22 23 fun1() 24 fun3("susen")
结果:
3、总结
按照上面一步步得来,应该能明白装饰器是怎么工作的了,基础知识有:非固定参数传参、函数名实际上也是一个变量(内存地址)、嵌套函数。明白以后,之后写代码就套用格式就完了。