一、说明
1.1 关于可变参数
所谓可变参数,最主要是指传递给被调用函数的参数的个数可以是不固定的。
可变参数应该来说是很常见的,比如C的标准main函数就写成int main(int argc, ** char argv),再比如很常用的print()函数就是最典型的可变参数函数。
但一方面在很长一段时间内并不能理解main函数其实和普通函数没什么区别,另一方面觉得print()是系统函数实现很复杂,所以一直都没弄懂如何实现可变参数应该传递。
1.2 关于注解
关于注解这个东西,最早是在大学学java的时候经常会看到某些方法上边@override之类的东西,一方面不知道其作用但另一方面似乎去掉也没什么影响,所以一直都不怎么在意。
今年去看开发的代码也看到很多注解,问其用途基本都和网上类似“为了开启XXX功能我们需要添加@XXX注解的”/”添加@XXX注解是为了开启XXX功能“,不知其原理感觉颇为难受所以自己来研究了一翻。
1.3 关于可变参数和注解有什么关系
注解和可变参数,在感觉上是没什么关系的。但当我去实现注解,发现要让注解可作用于不同参数个数的函数时需要解决可变参数问题。
而且应当来讲注解作用于不同参数个数的函数是个普遍的需求,所以注解和可变参数关系还是关联很大的。
二、可变参数实现本质
python中定义/调用函数时,参数有两种方式,一种是以位置参数(如test(a)),一种是键值参数(如test(a=1))。不管是定义还是调用,位置参数必须在键值形式参数之前。
使用的演示程序如下:
# 一个简单的求合函数
defcalc_sum(a, b, c, d, e):
sum_value = a + b + c + d +e
returnsum_value
# 此函数只单纯调用calc_sum()
def call_calc_sum(a,*args,**kwargs):
sum_value = calc_sum(a,*args,**kwargs)
returnsum_value
call_calc_sum(1, 2, 3, e=4, d=5)
2.1 从参数变为*args, **kwargs的过程
被调用函数通过以下步骤提取参数:
第一步,如果前面有非*args, **kwargs的参数,则在传来的参数中先分配给他。比如这里在*args前面有a,所以就把第一个参数值1赋给a。
第二步,将其他非k=v形式的参数,组成元组赋值为args。比如这是把下来的2,3组成(2,3)。
第三步,将其他的k=v形式的参数,组成字典赋值给kwargs。比如这里把e=4,d=4组成{'e': 4, 'd': 5}。
2.2 从*args, **kwargs变回具体参数的过程
被调用函数通过以下步骤提取参数:
第一步,如果开头有非*args, **kwargs的参数,则将按正常参数解析。如1赋给第一个参数a。
第二步,将元组参数按顺序分发给接下来的参数。如将2赋给下来的第二个参数b,再将3赋给下来的第三个参数c。
第三步,将字典参数,按设置的k/v分发给对应的参数。如按e=4赋给第五个参数e,按d=5赋值给第四个参数d。
2.3 直接就解析和使用*args和**kwargs
def calc_sum(a, *args, **kwargs):
sum_value=afor v inargs:
sum_value+=vfor k, v inkwargs.items():
sum_value+=v#另外如果想判断一个参数有没有被传参,可以通过if "key_word" in kwargs来实现
print(sum_value)
calc_sum(1, 2, 3, d=4, e=5)
2.4 函数定义是参数为*的作用
#强制给*号之后的变量传值,必须以k=v形式,不然会报错
def calc_sum(a, b, c, *, d, e):
sum_value= a + b + c + d +eprint(sum_value)#这样调用会报错#calc_sum(1, 2, 3, 4, e=5)#这样才能成功
calc_sum(1, 2, 3, d=4, e=5)
三、注解代码实现
3.1 最典型的注解函数实现
以下是一个最典型的注解函数的写法,自己写时只需要修改下被注解函数调用之前和调用之后的代码即可,其他部分完全可以原封不动照抄就完事了。
from functools importwraps#这一层用来接收被注解的函数的函数名(对于这里就是add_calc)。这一层必须存在
defdecorate_function(decorated_function):print(f"decorate_function--decorated_function: {decorated_function}")#这个注解的作用是保证其他函数调用被注解函数时,仍是被注解函数原名而不是注解函数名
#对于这里就是保证其他调用add_calc的函数,观测到的函数名仍是add_calc而不会变成decorate_function_real
#如果你并不关心这个差异,可以省掉这个注解
@wraps(decorated_function)#这一层用来接收被注解函数的参数(对于这里就是a, b),同时调用被注解函数。这一层必须存在
#*args, **kwargs的妙处就是不管被注解函数有几个参数都可以承接
def decorate_function_real(*args, **kwargs):print("decorate_function_real--do something before decorated_function exec...")#调用被注解函数,参数原封不动地传过去即可
result = decorated_function(*args, **kwargs)print("decorate_function_real--do something after decorated_function exec...")#这个return最后返回
#原封不动地向上层返回被注解函数的返回值
#如果被注解函数没有返回值,可以省掉这个返回
returnresult#这个return紧接着被返回,因为返回的是函数,所以返回后就被执行
returndecorate_function_real#一个简单的求合函数
@decorate_functiondefadd_calc(a, b):
add_result= a +bprint(f"add_calc--calc result: {add_result}")returnadd_resultif __name__ == "__main__":
a= 1b= 2add_result=add_calc(a, b)print(f"__main__--{a} + {b} = {add_result}")
执行结果如下:
3.2 把注解函数写在类内部
为了统一好看,可能我们想直接把注解函数也写在类内部,注解函数在类内部和在类外部写法上完全可以没区别。
from functools importwrapsclassTest:def __init__(self):print("Test--init")#这一层用来接收被注解的函数的函数名(对于这里就是add_calc)。这一层必须存在
defdecorate_function(decorated_function):print(f"decorate_function--decorated_function: {decorated_function}")#这个注解的作用是保证其他函数调用被注解函数时,仍是被注解函数原名而不是注解函数名
#对于这里就是保证其他调用add_calc的函数,观测到的函数名仍是add_calc而不会变成decorate_function_real
#如果你并不关心这个差异,可以省掉这个注解
@wraps(decorated_function)#这一层用来接收被注解函数的参数(对于这里就是a, b),同时调用被注解函数。这一层必须存在
#*args, **kwargs的妙处就是不管被注解函数有几个参数都可以承接
def decorate_function_real(*args, **kwargs):print("decorate_function_real--do something before decorated_function exec...")#调用被注解函数,参数原封不动地传过去即可
result = decorated_function(*args, **kwargs)print("decorate_function_real--do something after decorated_function exec...")#这个return最后返回
#原封不动地向上层返回被注解函数的返回值
#如果被注解函数没有返回值,可以省掉这个返回
returnresult#这个return先被返回,因为返回的是函数,所以返回后就被执行
returndecorate_function_real#一个简单的求合函数
@decorate_functiondefadd_calc(self, a, b):
add_result= a +bprint(f"Test--calc result: {add_result}")returnadd_resultif __name__ == "__main__":
obj=Test()
a= 1b= 2add_result=obj.add_calc(a, b)print(f"__main__--{a} + {b} = {add_result}")
View Code
运行结果如下:
3.3 对类进行注解
有时我们会看到有对类进行注解的写法,对类进行注解,其实质相当于对类的__init__函数进行注解。一般用于统计类被实例化了多少次。
from functools importwraps#这一层用来接收被注解的函数的函数名(对于这里就是add_calc)。这一层必须存在
defdecorate_function(decorated_function):print(f"decorate_function--decorated_function: {decorated_function}")#这个注解的作用是保证其他函数调用被注解函数时,仍是被注解函数原名而不是注解函数名
#对于这里就是保证其他调用add_calc的函数,观测到的函数名仍是add_calc而不会变成decorate_function_real
#如果你并不关心这个差异,可以省掉这个注解
@wraps(decorated_function)#这一层用来接收被注解函数的参数(对于这里就是a, b),同时调用被注解函数。这一层必须存在
#*args, **kwargs的妙处就是不管被注解函数有几个参数都可以承接
def decorate_function_real(*args, **kwargs):print("decorate_function_real--do something before decorated_function exec...")#调用被注解函数,参数原封不动地传过去即可
result = decorated_function(*args, **kwargs)print("decorate_function_real--do something after decorated_function exec...")#这个return最后返回
#原封不动地向上层返回被注解函数的返回值
#如果被注解函数没有返回值,可以省掉这个返回
returnresult#这个return先被返回,因为返回的是函数,所以返回后就被执行
returndecorate_function_real
@decorate_functionclassTest:def __init__(self):print("Test--init")#一个简单的求合函数
defadd_calc(self, a, b):
add_result= a +bprint(f"Test--calc result: {add_result}")returnadd_resultif __name__ == "__main__":
obj=Test()
a= 1b= 2add_result=obj.add_calc(a, b)print(f"__main__--{a} + {b} = {add_result}")
View Code
执行结果如下,可以看到注解函数只在__init__函数被调用时运行:
3.4 使用类进行注解
在更少数情况下,我们还会看到使用类进行注解。
classdecorate_class:#__init__函数用于承接被注解函数函数名
def __init__(self, decorated_function):
self.decorated_function=decorated_functionprint("decorate_class--init")#__call__函数用来承接被注解函数的参数
def __call__(self, *args, **kwargs):print("decorate_function_real--do something before decorated_function exec...")#调用被注解函数,参数原封不动地传过去即可
result = self.decorated_function(*args, **kwargs)print("decorate_function_real--do something after decorated_function exec...")#这个return最后返回
#原封不动地向上层返回被注解函数的返回值
#如果被注解函数没有返回值,可以省掉这个返回
returnresult#一个简单的求合函数
@decorate_classdefadd_calc(a, b):
add_result= a +bprint(f"Test--calc result: {add_result}")returnadd_resultif __name__ == "__main__":
a= 1b= 2add_result=add_calc(a, b)print(f"__main__--{a} + {b} = {add_result}")
View Code
执行结果如下:
3.5 注解函数/类带参数的写法
前面4小节的写法中我们的注解函数/类都是没有带有参数的,当我们想不管三七二十一给注解函数传递参数时会引发报错。但有时我们确实会看到有些注解函数就是可以带参数的,如果我们就想给注解函数传递参数,我们该如何实现呢。
在不带参数的场景中,外层函数承接到的是被注解函数函数名、内层函数承接被注解函数的参数。当给注解函数传递参数时,外层函数承接到的是注解函数的参数、中间层函数承接到的是被注解函数函数名、内层函数承接被注解函数的参数。
总之,当要给注解函数传递参数时,要从不带参数时的两层函数结构,变成三层函数结构(对于注解类而言,就在__call__函数内再加一层函数)。
实现示例如下:
from functools importwraps#这一层用来接收注解函数的参数(对于这里就是"111", "222")。
defdecorate_function_outer(param1, param2):print(f"decorate_function_outer--param1: {param1}")print(f"decorate_function_outer--param2: {param2}")#这一层用来接收被注解的函数的函数名(对于这里就是add_calc)。这一层必须存在
defdecorate_function_inner(decorated_function):#这个注解的作用是保证其他函数调用被注解函数时,仍是被注解函数原名而不是注解函数名
#对于这里就是保证其他调用add_calc的函数,观测到的函数名仍是add_calc而不会变成decorate_function_real
#如果你并不关心这个差异,可以省掉这个注解
print(f"decorate_function_inner--decorated_function: {decorated_function}")
@wraps(decorated_function)#这一层用来接收被注解函数的参数(对于这里就是a, b),同时调用被注解函数。这一层必须存在
#*args, **kwargs的妙处就是不管被注解函数有几个参数都可以承接
def decorate_function_real(*args, **kwargs):print("decorate_function_real--do something before decorated_function exec...")#调用被注解函数,参数原封不动地传过去即可
result = decorated_function(*args, **kwargs)print("decorate_function_real--do something after decorated_function exec...")#这个return最后返回
#原封不动地向上层返回被注解函数的返回值
#如果被注解函数没有返回值,可以省掉这个返回
returnresult#这个return紧接着被返回,因为返回的是函数,所以返回后就被执行
returndecorate_function_real#这个return最先被返回,因为返回的是函数,所以返回后就被执行
returndecorate_function_inner#一个简单的求合函数
@decorate_function_outer("111", "222")defadd_calc(a, b):
add_result= a +bprint(f"add_calc--calc result:: {add_result}")returnadd_resultif __name__ == "__main__":
a= 1b= 2add_result=add_calc(a, b)print(f"__main__--{a} + {b} = {add_result}")
View Code
运行结果如下:
参考: