一、说明
1.1 关于注解
关于注解这个东西,最早是在大学学java的时候经常会看到某些方法上边@override之类的东西,一方面不知道其作用但另一方面似乎去掉也没什么影响,所以一直都不怎么在意。
今年去看开发的代码也看到很多注解,问其用途基本都和网上类似“为了开启XXX功能我们需要添加@XXX注解的”/”添加@XXX注解是为了开启XXX功能“,不知其原理感觉颇为难受所以自己来研究了一翻。
1.2 关于可变参数
所谓可变参数,最主要就是指传递给被调用函数的参数的个数是不定的。
可变参数应该来说是很常见的,比如C的标准main函数就写成int main(int argc, ** char argv),再比如很常用的print()函数就是最典型的可变参数函数。
但一方面在很长一段时间内并不能理解main函数其实和普通函数没什么区别,另一方面觉得print()是系统函数实现很复杂,所以一直都没弄懂如何实现可变参数应该传递。
1.3 关于注解和可变参数有什么关系
注解和可变参数,在感觉上是没什么关系的。但当我去实现注解,发现要让注解可作用于不同参数个数的函数时需要解决可变参数问题。
而且应当来讲注解作用于不同参数个数的函数是个普遍的需求,所以注解和可变参数关系还是关联很大的。
二、注解代码实现
2.1 被注解函数无参数
# 一个用于进行修饰的函数 # 关键一:外层函数有且只有一个参数,该参数用于承接被修饰函数本身 def decorate_function(need_decorate_function_name): def decorated_function(): # 关键二:在调用被修饰函数前/后做些其他事情 print("calc staring...") # 关键点三:原封不动地调用被修饰函数 need_decorate_function_name() print("calc finished...") # 关键点四:在最后把修饰完后的函数return回去 return decorated_function # 一个简单的求合函数 @decorate_function def calc_sum(): a = 1 b = 2 sum_value = a + b print(f"{a} + {b} = {sum_value}") if __name__ == "__main__": calc_sum()
最终执行结果如下:
2.2 被注解函数有参数
# 一个用于进行修饰的函数 def decorate_function(need_decorate_function_name): # 关键点一:内层函数使用和被修饰函数完全一样的参数去承接即可 # 当然参数名一不一样本来其实无所谓,但为了省事全都一样即可 def decorated_function(a, b): print("calc staring...") # 关键点二:内层函数将接收到的参数又再原封不动地传给被修饰函数即可 need_decorate_function_name(a, b) print("calc finished...") return decorated_function # 一个简单的求合函数 @decorate_function def calc_sum(a, b): sum_value = a + b print(f"{a} + {b} = {sum_value}") if __name__ == "__main__": calc_sum(1, 2)
最终执行结果如下:
2.3 被注解函数有返回值
# 一个用于进行修饰的函数 def decorate_function(need_decorate_function_name): def decorated_function(a, b): print("calc staring...") # 关键点一:承接好被修饰函数的返回值 result = need_decorate_function_name(a, b) print("calc finished...") # 关键点二:在末尾将被修饰函数的返回值原封不动地向上层返回 return result return decorated_function # 一个简单的求合函数 @decorate_function def calc_sum(a, b): sum_value = a + b return sum_value if __name__ == "__main__": a = 1 b = 2 sum_value = calc_sum(a, b) print(f"{a} + {b} = {sum_value}")
执行结果如下:
2.4 被注解函数有多个且它们的参数个数不一致
# 一个用于进行修饰的函数 def decorate_function(need_decorate_function_name): # 关键点一:使用*args, **kwargs承接所有参数 def decorated_function(*args, **kwargs): print("calc staring...") # 关键点二:一模一样地直接把*args, **kwargs传给被修饰函数即可 result = need_decorate_function_name(*args, **kwargs) print("calc finished...") return result return decorated_function # 一个简单的求合函数 @decorate_function def calc_sum_2(a, b): sum_value = a + b return sum_value # 一个简单的求合函数 @decorate_function def calc_sum_3(a, b, c): sum_value = a + b + c return sum_value if __name__ == "__main__": a = 1 b = 2 c = 3 sum_value_2 = calc_sum_2(a, b) print(f"{a} + {b} = {sum_value_2}") sum_value_3 = calc_sum_3(a, b, c) print(f"{a} + {b} + {c} = {sum_value_3}")
执行结果如下:
2.5 在类内使用注解
class Test: # 一个用于进行修饰的函数 # 关键点一:坚定不移地认同,外层函数有且只有一个参数,该参数用于承接被修饰函数 def decorate_function(need_decorate_function_name): # 关键点二:坚定不移地认同*args, **kwargs可以承接所有参数,包括self在内 def decorated_function(*args, **kwargs): print("calc staring...") # 关键点三:坚定不移地认同*args, **kwargs可以把所有参数传给被修饰函数,包括self在内 result = need_decorate_function_name(*args, **kwargs) print("calc finished...") return result return decorated_function # 一个简单的求合函数 @decorate_function def calc_sum_2(self, a, b): sum_value = a + b return sum_value # 一个简单的求合函数 @decorate_function def calc_sum_3(self, a, b, c): sum_value = a + b + c return sum_value if __name__ == "__main__": obj = Test() a = 1 b = 2 c = 3 sum_value_2 = obj.calc_sum_2(a, b) print(f"{a} + {b} = {sum_value_2}") sum_value_3 = obj.calc_sum_3(a, b, c) print(f"{a} + {b} + {c} = {sum_value_3}")
执行结果如下:
三、可变参数实现本质
python中调用函数时,传递参数有两种方式,一种是以位置形式进行传递(如test(a)),一种是以“k=v”的的形式进行传递(如test(a=1))。同样的“k=v”形式必须位于位置参数之后。
(另外,python中定义一个函数其参数有类似的两种形式,一种是没有默认值的参数(位置参数,如def test(a)),一种是有默认值的参数(默认参数,def test(a=1))。另外默认参数必须处于位置参数之后。但一是我们这里参数传递并不需要关心函数定义时参数的形式)
使用的演示程序如下:
# 一个简单的求合函数 def calc_sum(a, b, c, d, e): sum_value = a + b + c + d + e return sum_value # 此函数只单纯调用calc_sum() def call_calc_sum(a,*args,**kwargs): sum_value = calc_sum(a,*args,**kwargs) return sum_value call_calc_sum(1, 2, 3, e=4, d=5)
3.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]。
3.2 从*args, **kwargs变回具体参数的过程
被调用函数通过以下步骤提取参数:
第一步,如果开头有非*args, **kwargs的参数,则将按正常参数解析。如1赋给第一个参数a。
第二步,将元组参数按顺序分发给接下来的参数。如将2赋给下来的第二个参数b,再将3赋给下来的第三个参数c。
第三步,将字典参数,按设置的k/v分发给对应的参数。如按e=4赋给第五个参数e,按d=5赋值给第四个参数d。