函数修饰器
参考链接:Python学习笔记11:函数修饰符 - 魔芋红茶 - 博客园 (cnblogs.com)
目录
Python有很多有趣的特性,其中函数修饰符就是一个。
我们在之前的那个web应用示例中用过如下写法:
@web.route('/log')
@符号后边的,就是一个函数修饰符,它可以在不改变原有函数的情况下改变函数的行为(通常来说是增强函数的行为)。
我们下面就来说说怎样实现一个函数修饰符。
在这之前,我们要先介绍几个必须要先掌握的内容。
接收和返回函数
如我们在Python学习笔记0:变量中讨论的,在Python中,所有的一切都是对象,而函数也不例外。那理所应当的,函数也可以作为对象被另一个函数所接收和返回。
我们看下边的例子:
def receiveFunc(func):
print(type(func))
return func
def hellow():
print("hellow world")
returned=receiveFunc(hellow)
returned()
输出
<class 'function'>
hellow world
我们可以看到,receiveFunc
接收了函数hellow
并打印出其类型,然后返回,返回的函数依然可以正常执行并输出结果。
可变参数列表
我们都知道,在函数签名的定义中,参数数目可以是一个,也可以是多个,但都是定义的时候就指定的,但Python还有另一种定义方法,可以接收数目不定的参数。
def getMultipParam(*multipParams):
for param in multipParams:
print(param,' ',end='')
print()
getMultipParam(1,2,3)
getMultipParam('a','b','c','d','e')
输出
1 2 3
a b c d e
通过类似*params
这种方式,可以指定参数接收的是可变长度的参数列表,这种方式有点像C++中的指针,可以接收一个数组作为参数,而在Python这里,就是接收一个列表,本质上接收函数会把收到的参数列表转变为一个列表进行处理。
当然,你也可以直接传一个列表过去,但是需要用*
来指定:
def getMultipParam(*multipParams):
for param in multipParams:
print(param,' ',end='')
print()
getMultipParam(*[1,2,3])
getMultipParam(*['a','b','c','d','e'])
输出
1 2 3
a b c d e
可以看出两者是等价的。但是这么做是有意义的,其目的在于要用*
来说明传过去的列表是作为可变参数列表来传递,而非是仅仅一个普通参数。当然,如果不这么做接收函数就会认为仅仅传入了一个参数,是个列表。
到这里,关于可变参数的内容说完了吗?并没有。
如果大家还记得,参数传递还有另一种方式,即指明参数名称,进行一对一的传递。如果是那种方式,可变参数要如何定义呢?
def getKVParams(**kvParams:dict):
for paramKey,paramVal in kvParams.items():
print(paramKey,':',paramVal,end=',')
print()
getKVParams(name="jack",age=16)
getKVParams(career="enginner",age=18)
输出
name : jack,age : 16,
career : enginner,age : 18,
和之前的定义类似,不过是将传入参数作为字典来处理,相应的,也同样可以直接用**把可变参数列表作为字典来传入:
def getKVParams(**kvParams:dict):
for paramKey,paramVal in kvParams.items():
print(paramKey,':',paramVal,end=',')
print()
getKVParams(**{"name":"jack","age":16})
getKVParams(**{"career":"teacher","age":17})
输出
name : jack,age : 16,
career : teacher,age : 17,
你以为到这里就结束了?
太天真,我们还可以把这两者结合起来!
def getEveryParams(*multipParams,**kvParams:dict):
for param in multipParams:
print(param,end=',')
print()
for paramKey,paramVal in kvParams.items():
print(paramKey,':',paramVal,end=',')
print()
getEveryParams(1,2,3,age=16,name="jack")
输出:
1,2,3,
age : 16,name : jack,
可以看到,通过将两种方式结合,我们的函数就可以接收任何形式的传参了,至于这有什么用,不用急,看完这篇博客就能明白了。
关于前置知识,还有最后一个,内部函数。
内部函数
相信对Java不陌生的朋友应该会在此时说,这个我会,内部类!
不错,Python的内部函数在我看来和Java的内部类颇为类似,而且事实上两者也是一致的,因为我一直所强调的,在Python里,一切都是对象,所以Python会存在和Java内部类高度类似的东西也不足稀奇。
def getInnerFunc():
def innerFunc():
print("hellow world")
return innerFunc
test=getInnerFunc()
test()
输出
hellow world
可以看到,getInnerFunc
函数的内部定义了一个内部函数,然后将这个函数返回,而外部程序拿到返回的函数以后也可以正常执行。而这种使用方式也恰恰是内部函数的最常用方式。
函数修饰符
好了,终于要讲到我们的主题了,如何构造一个我们自己的函数修饰符。
其实其核心要点就是把我们之前讲的三种技巧结合起来。
我们始终要有这样的概念,函数修饰符的作用是将一个已有的函数增强其用途。
这种思想可以用现实的例子来类比,就拿我喜欢的军事来说吧。现在的主战坦克在真正作战中肯定不会裸奔,需要按作战行为增加诸多附加模块,比如悬挂附加装甲,增加自动反导弹装置,还有眼目发生器和红外干扰装置之类的更是稀松平常。
但是这些最重要的是什么,是我们在没有改变坦克本身的情况下增强了坦克的作战性能。
是不是很酷?
我们现在就在Python中这么做吧。
我们先定义一个坦克:
def tank(player1:'驾驶员',player2:'装填手',player3:'车长',player4:'炮长'):
print("这是一个裸奔的坦克")
print("该坦克有四名成员")
别问为什么这里没有用英语。。。
我们现在用坦克工厂给它加装反应装甲:
def tank(player1:'驾驶员',player2:'装填手',player3:'车长',player4:'炮长'):
print("这是一个裸奔的坦克")
print("该坦克有四名成员")
def tankFactory(tank:function)->function:
def powerTank():
tank()
print("加装反应装甲")
print("加装反导弹装置")
print("加装红外干扰仪")
return powerTank
可以看到,我们利用上边说的两个特性来构建了一个坦克工厂,来增强进入工厂的坦克。
等等,我们是不是忘掉了什么,对,我们这里忘记了使用可变参数,我们需要注意到,上边的示例是有问题的,比如从工厂返回的powerTank
居然没有参数,这就相当于这个增强版坦克顶盖被焊死了,不能进驾驶员,这显然是不合理的,没有部队会接受这样的改装。
那我们像tank
的定义一样指定四个成员作为参数行不行?当然是可以的,但是这同样有问题,比如现在一些先进坦克是自动装填,不需要炮手,只要三个成员,那你这工厂就不能接受这种坦克了,这相当有局限性。
所以,这里就是我们的可以接受任何类型参数的技巧的作用所在了,让我们改进一下我们的坦克工厂:
def tankFactory(tank:"function")->"function":
def powerTank(*params:tuple,**kvParams:dict):
tank(*params,**kvParams)
print("已加装反应装甲")
print("已加装反导弹装置")
print("已加装红外干扰仪")
return powerTank
@tankFactory
def tank(player1:'驾驶员',player2:'装填手',player3:'车长',player4:'炮长'):
print("这是一个裸奔的坦克")
print("该坦克有四名成员")
@tankFactory
def mordenTank(player1,player2,player3):
print("这是一个3成员的现代坦克")
tank("tom","jerry","robot1","robot2")
mordenTank("tom","jerry","robot")
输出:
这是一个裸奔的坦克
该坦克有四名成员
已加装反应装甲
已加装反导弹装置
已加装红外干扰仪
这是一个3成员的现代坦克
已加装反应装甲
已加装反导弹装置
已加装红外干扰仪
可以看到,我们现在的坦克工厂非常棒,通过在要改装的坦克函数前简单的加上@tankFactory
就可以直接改装。真是太方便了。
等等,在你准备撸袖子改装掉所有类型的坦克前,还需要注意一点,因为Python中所有函数都是对象,而作为函数修饰符的函数也不例外,而解释器在处理这种特殊的函数时,有时候会忘记这是一个函数修饰符。所以我们需要显示地告诉Python解释器,这是一个作为函数修饰符的特殊函数,而非普通货色。
当然,这有点奇怪,但我们要做的是接受它。
至于如何做,很简单:
从functools
模块引入一个函数wraps
,并在函数修饰符中调用这个函数。
from functools import wraps
def tankFactory(tank:"function")->"function":
@wraps(tank)
def powerTank(*params:tuple,**kvParams:dict):
tank(*params,**kvParams)
print("已加装反应装甲")
print("已加装反导弹装置")
print("已加装红外干扰仪")
return powerTank
@tankFactory
def tank(player1:'驾驶员',player2:'装填手',player3:'车长',player4:'炮长'):
print("这是一个裸奔的坦克")
print("该坦克有四名成员")
@tankFactory
def mordenTank(player1,player2,player3):
print("这是一个3成员的现代坦克")
tank("tom","jerry","robot1","robot2")
mordenTank("tom","jerry","robot")
这就是最终的代码了。
关于函数修饰符的内容已经讲完了,但其实还有一些扩展性的问题可以讨论,比如函数修饰符这种叫法很怪异和别扭,其实叫做修饰器更妥当,因为其本质就是设计模式中的修饰器模式。还有函数修饰符和我们前边讲的上下文模式也颇为类似,是不是可以用函数修饰符实现一个上下文模式?