【问题描述】
请设计一个函数修饰器,使之能输出函数的调用信息,包括函数名、参数值和返回值。
输出结果:
multiply(4,b=7)返回值:28
备注:multiply是函数名,第1个参数值为4,第2个参数名为b,值为7,函数的返回值为28。
【题前思考】
根据问题描述,填写表5-3-1。
表5-3-1 问题分析
【解题思路】
假设函数名为fun,则其属性__name__就可以取得与函数名相同的字符串“fun”。函数的参数分为位置参数和关键字参数两种,函数调用中写了参数名的就是关键字参数,没有写参数名的就是位置参数,如multiply(4,b=7)中4就是位置参数,b=7就是关键字参数。Python中所有位置参数可以形成一个列表,所有关键字参数可以构成一个字典,所以不管函数的参数是什么都可以归结为两个参数,一个列表和一个字典。可以定义一个包装函数以目标函数fun为参数,在这个函数里面取得相关信息输出之后,再调用目标函数fun。这个包装函数就是函数修饰器。
【程序代码】
【代码分析】
①:定义函数funinfo(fun)用来产生并返回函数fun的包装器函数wrapper。函数名funinfo就是函数装饰器的名称。
②:定义函数wrapper(*args,**kargs)用于包装函数fun。参数*args表示用形参中的所有位置参数构成一个列表,列表名为args。**kargs表示用形参中的所有关键字参数构成一个字典,字典名为kargs。
③:将所有位置参数追加到列表alist后面。
④:将所有关键字参数追加到列表alist后面。
⑤:调用目标函数fun,将返回值保存到变量r。这是包装器函数必须要完成的工作,不然就与目标函数没有关系了。如果需要对参数进行一些前置处理就可以放到调用目标函数以前,如本例中的获取参数信息。如果要对函数进行一些善后处理,如检查返回值的范围,就可以在调用后进行。
⑥:包装器函数wrapper将目标函数的返回值r返回给调用者。返回的值可以是目标函数返回的值,也可以是处理之后的值,这由程序设计需要决定。
⑦⑧:将目标函数的__name__属性和__doc__属性复制到包装函数wrapper,这样包装函数wrapper就完全变成了目标函数fun。
⑨:返回包装器函数作为目标函数fun的替代品。以后对目标函数fun的调用其实会变成对包装器函数的调用。
⑩:对函数fun应用函数修饰器funinfo。
调用funinfo(multiply)后,它会返回包装器函数wrapper,并赋值给变量multiply,而函数multiply的“真身”也会存放到包装器wrapper中,以后对函数multiply的调用就变成了对wrapper的调用,而wrapper函数会在调用函数返回结果之前输出函数的调用信息。
【优化提升】
在上例中使用了wrapper.__name__=fun.__name__和wrapper.__doc__=fun.__doc__来保存目标函数fun的部分信息,在内置模块functools中提供了一个函数修饰器wraps用来保存更多目标函数的信息。修改之后的代码如下:
使用了functools修饰后,我们可以从包装器函数wrapper中获得更多关于目标函数fun的信息。
【技术全貌】
除了提供wraps函数修饰器外,functools模块还提供了很多有用的功能帮助我们高效使用函数,部分函数见表5-3-2。
表5-3-2 functools模块函数
续表