装饰器:
简单的说,装饰器就是修改函数功能的函数。它可以让你的代码更简洁,且易于代码维护。下面我们就一步一步来揭开python函数装饰器的面纱。
一切皆对象:
首先我们定义一个简单的函数,给函数一个默认参数name='yiha':
打印函数执行结果如下图:
python中一切皆对象,无论是数值,列表,甚至函数都可以看作对象。所以函数也可以被当作一个对象赋值给变量。那么该怎么进行函数的赋值呢?没错直接将函数名称当作对象赋值给变量即可;如上图的函数,我们可以像下图的方法一样,将函数赋值给其它变量:
上图中在hi后面我们没有使用小括号,因为我们并不是在调用hi函数,而是将函数放在hello中:
此时我们执行变量hello就可以得到和执行hi相同的结果:
若我们删除掉函数名称hi,再去打印hi( )运行的结果,发现hi( )函数运行报错,报错内容说名称没有被定义:
但是当我们打印hello( )执行的结果时发现依旧能够执行出hi( )函数的动作:
这就说明函数名称就和变量名一样,只是个引用,删除其并不会改变函数本身的功能。
以上的过程主要是为了得出一个结论:函数和其它数据和数据结构一样,在python中都被当作对象看待,可以被赋予给另外一个变量,这样函数就可以通过这个变量传递。 这是学习装饰函数首先要理解的!
函数中定义函数(函数的嵌套):
函数中还可以嵌套函数,方法如下图:我们先定义了一个函数hi( ) ,在hi( ) 内部定义了两个函数 greet( ) 和 welcome( ) ,要使函数内部定义的函数产生效果,还必须在函数内部执行。
现在我们运行主函数hi( ),运行结果如下图:
由上面的运行结果,我们发现这个函数的整个执行过程:先由于hi( ) 的执行,指针跳转到“ def hi( name=yassob)” ===> 然后执行下一行打印 ===> 接下来两个函数由于没有调用所以所以先不执行,直到后面调用时分别执行内部函数的代码块,分别打印了两行内容 ===> 最后再执行代码最后一行。
但是注意函数内部定义的函数在函数外部是不能执行的,就相当于局部变量只能在定义其的 函数内部执行。如下图:
由于装饰器中也就是通过函数的嵌套来给目标函数修改功能的,所以理解函数的嵌套是学习装饰器的前提。
从函数中返回函数:
上一步的实验我们知道了函数中可以定义函数,并且在函数外部不能执行函数内部定义的函数; 那么怎样才能在函数外部执行函数内部定义的函数呢? 这时候就需要从函数中返回函数内部定义的函数。
下图定义了一个嵌套函数,我们如何能在函数外部执行函数green( )呢? 没错,关键是第53行 和 55行的操作,没错,就是返回函数本身,而不执行函数,什么就函数本身呢? 即函数这个对象,你可以回想一下我们在第一个标题“一切皆对象”的部分 将函数这个对象赋给另一个变量,函数名称就代表函数这个对象,所以53行 或者 55行 是 return green 和return yellow,注意!注意!注意!只是返回了函数本身,没有执行函数,所以函数名称后面没有括号!!!
接下来我们来尝试使函数green( )执行。
直接执行green( ) 肯定会报错:
在上面的代码块中,当name = 'haya' 时,我们在外部函数中就可以返回green,即 函数本身,注意不是函数执行的结果!!!
所以执行 hi(name='haya') 后返回 green ,然后将green赋值个变量,再执行变量,这时候就能j间接执行函数green( ),过程如下:
其实 a = hi( ) 以及 a( ) 这两步就相当于 hi( )( ) 。
函数可以作为实参数传递给另一个函数:
现在我们就离写第一个装饰函数很近了。
如下面我们定义了两个函数,第一个函数是打印一行句子,我们已经提到过,装饰器的作用就是给某个函数添加新功能; 假如我们要给函数hi( ) 添加一个新的动作,第一个选择是直接在函数hi( ) 中加需要的功能代码,这样做可以,但是,若我们要为成千上万的函数中都添加某一动作,比如,圣诞节到了,你要在你们公司的每个页面都显示圣诞快乐,这时你每个函数一句一句去添加就不太现实了。
第二个选择是,你写一个函数fun( ),fun函数的功能就是显示圣诞快乐,接着,你将fun函数本身作为参数传递给你要添加fun函数功能的函数,最后你在该函数中调用fun( )函数,这样好像就比刚才简单好多了,过程如下图:
执行后结果如下图:
这个第二个选择相对第一个选择来说,确实方便了一点点,但是也很麻烦,首先若有多个函数需要添加圣诞快乐,则需要复制多块代码,然后在代码块中调用要添加功能的函数,这么多的重复代码块明显代码执行效率是不高的; 其次,当圣诞节到了的时候,你写了一个上面的代码块给某个页面打印圣诞快乐,然后复制用到多个页面,没过几天,元旦来了,接着春节来了……
后面发生的事情自行脑补。
不过,没事,你还有第三个选择,那就是装饰器。
第一个装饰器:
装饰器的作用是:将目标函数进行封装,添加相应的功能,进而改变或者丰富函数的行为。
如果上面的几个知识点都理解了的话,下图这个函数应该不难理解:
没错,这就是写一个装饰函数的第一步。 87行定义的函数a_function_need_decorated( )是我们要给其加功能的函数。 79行定义的函数first_decorate是装饰器的开始。而真正给函数添加功能的是80行定义的内部函数inner( )。这个装饰器要添加的功能是在执行目标函数a_dunction_need_decorated( )的打印的句子的前后,分别打印一句话。
接下来执行经过装饰函数“化妆“后的函数的行为,做如下两步操作:
上图第一行是将目标函数对象a_function_need_decorated作为参数传递给装饰函数first_decarate( ),由上上方图中的代码块可知:执行装饰函数后会将内部函数inner( ) 这一函数对象返回回来,这时候上图第一行就相当于 a_function_need_decorated = inner ,意思是将inner函数的对象赋给a_function_need_decarated,即将inner( ) 函数的整个函数代码块赋给变量a_function_need_decarated。
第二行是执行a_function_need_decarated( ) 函数,也就是执行inner( ) 函数。执行结果如下图:
如上图明显达成了我们的预期。首先最后执行函数的时候,我们依旧使用的是目标函数的函数名称; 其次当函数需要在原有功能上进行修改时,只需要修改装饰函数就可以了,不影响目标函数的代码结构。
以上就是装饰函数的大致雏形了,但是这样貌似还是有点麻烦,因为在写好装饰函数后,要想执行目标函数就能使装饰函数添加的新功能实现,还得将目标函数作为参数传递给装饰函数,再将其返回值赋值给和目标函数名称一样的变量,之后才能达到执行目标函数后,我们要的装饰效果。 所以,我们还能更简洁一点,那就得依靠语法糖的作用!
语法糖:让装饰函数使用起来更简单、方便
还是以上面的装饰函数为例,装饰函数不变,只需在目标函数的上面添加语法糖,格式为@ +装饰函数名称, 如104行:
其实104行语法糖的效用就与右方代码相同: a_function_need_decorated = first_decorate(a_function_need_decorated),
明显语法糖是我们的代码写起来更方便,以后要是想对哪个目标函数进行装饰,只需要加上语法糖即可。
我们来测试看看语法糖究竟有没有达到我们要的效果:
问题一:装饰函数重写了函数名 和 函数注释,怎么保留被装饰函数的原有名称和注释?
我们在写函数的时候,习惯在定义了函数名称后,添加一行注释来描述函数,所以得到正确的注释这是必要的。
我么来做个测试,还是上面的装饰函数和目标函数,我们给inner( ) 函数 和 a_function_need_decorated( )函数 加上注释:
接着用代码返回a_ function_need_decorated( ) 的函数名称和注释,测试是否与真实情况符合:
如下图,返回的结果明显与真实情况不符合,这里返回的是函数inner的名称和注释。
我们可以做如下的操作解决这一问题第一步如112行导入wraps模块;第二步如115行,在真正实现装饰功能的函数inner( )的定义前加上@wraps(func),func是形式参数,将来接收的是目标函数名称:
然后再测试,如下图,函数名称和注释就是a_function_need_decorate( )函数的名称和注释,显示的结果没问题了:
问题二:当目标函数有返回值时,怎么让装饰后的函数正常返回返回值
比如下方两个函数,函数有返回值:
这时候要使我们的装饰后的函数依旧能返回被装扮的函数的执行结果,如下图我们要做16 行和 19 行的操作,接收目标函数的返回值赋给另外一个变量,并且在内部函数中将变量作为返回值。
不做上面两步的处理的执行结果:
做过处理后的执行结果: