一、說明
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。