詳解Python 函式引數的拆解
本文為閱讀 《Python Tricks: The Book》一書的 3.5 Function Argument Unpacking 的筆記與擴充理解。函式引數拆解是定義可變引數(VarArgs) *args
和 **kwargs
的反向特性。
*args
和 **kwars
是函式可定義一個形參來接收傳入的不定數量的實參。
而這裡的函式引數拆解是形參定義多個,在呼叫時只傳入一個集合型別物件(帶上 * 或 ** 字首),如 list
,tuple
,dict
,甚至是 generator
,然後函式能自動從集合物件中取得對應的值。
如果能理解下面賦值時的引數拆解和 Python 3.5 的新增 *
**
唯一的不同時作為引數的集合傳入函式時必須前面加上 *
或 **
,以此宣告該引數將被拆解,而非一個整體作為一個函式引數。加上 *
或 **
與 Java 的 @SafeVarargs
有類似的功效,最接近的是 Scala 的 foo(Array[String]("d","e") : _*)
寫法。參見:Java 和 Scala 呼叫變參的方式
Python 的賦值拆解操作
>>> a,b = [1,2] # a,b = (1,2) 也是一樣的效果 >>> print(a,b) 1 2 >>> a,b = {'x': 1,'y':2} >>> print(a,b) x y >>> a,'y':2}.keys() >>> print(a,'y':2}.values() >>> print(a,b = (x * x for x in range(2)) >>> print(a,b) 0 1
Python 3.5 的新增拆解操作
>>> [1,2,*range(3),*[4,5],*(6,7)] # * 號能把集合打散,flatten(unwrap) [1,1,4,5,6,7] >>> {'x': 1,**{'y': 2,'z': 3}} # ** 把字典打散,flatten(unwrap) 操作 {'x': 1,'y': 2,'z': 3}
有些像是函式程式設計中的 flatten
或 unwrap
操作。
有了上面的基礎後,再回到原書中的例子,當我們定義如下列印 3-D 座標的函式
def print_vector(x,y,z): print('<%s,%s,%s>' % (x,z))
依次傳入三個引數的方式就不值不提了,現在就看如何利用函式的引數拆解特性,只傳入一個集合引數,讓該 print_vector
函式準確從集合中獲得相應的 x
,y
,和 z
的值。
函式引數拆解的呼叫舉例
>>> list_vec = [2,3] >>> print_vector(*list_vec) <2,3> >>> print_vector(*(2,3)) <2,3> >>> dict_vec = {'y': 2,'z': 1,'x': 3} >>> print_vector(*dict_vec) # 相當於 print_vector(*dict_vec.keys()) <y,z,x> >>> print_vector(**dict_vec) # 相當於 print_vector(dict_vec['x'],dict_vec['y'],dict_vec['z'] <3,1> >>> genexpr = (x * x for x in range(3)) >>> print_vector(*genexpr) <0,4> >>> print_vector(*dict_vec.values()) # 即 print_vector(*list(dict_vec.values())) <2,3>
注意 **dict_vec
有點不一樣,它的內容必須是函式 print_vector
的形參 'x','y','z' 作為 key 的三個元素。
以下是各種錯誤
**dict_vec
元素個數不對,或 key 不匹配時的錯誤
>>> print_vector(**{'y': 2,'x': 3}) <3,1> >>> print_vector(**{'y': 2,'a': 3}) #元素個數是3 個,但出現 x,z 之外的 key Traceback (most recent call last): File "<pyshell#39>",line 1,in <module> print_vector(**{'y': 2,'a': 3}) TypeError: print_vector() got an unexpected keyword argument 'a' >>> print_vector(**{'y': 2,'x': 3,'a': 4}) # 包含有 x,但有四個元素,key 'a' 不能識別 Traceback (most recent call last): File "<pyshell#40>",'a': 4}) TypeError: print_vector() got an unexpected keyword argument 'a' >>> print_vector(**{'y': 2,'z': 1}) # 缺少 key 'x' 對應的元素 Traceback (most recent call last): File "<pyshell#41>",'z': 1}) TypeError: print_vector() missing 1 required positional argument: 'x'
不帶星星的錯誤
>>> print_vector([2,3]) Traceback (most recent call last): File "<pyshell#44>",in <module> print_vector([2,3]) TypeError: print_vector() missing 2 required positional arguments: 'y' and 'z'
把集合物件整體作為第一個引數,所以未傳入 y 和 z,因此必須用字首 * 或 ** 通告函式進行引數拆解
集合長度與函式引數個數不匹配時的錯誤
>>> print_vector(*[2,1]) # 拆成了 x=2,y=1,然後 z 呢? Traceback (most recent call last): File "<pyshell#47>",in <module> print_vector(*[2,1]) TypeError: print_vector() missing 1 required positional argument: 'z' >>> print_vector(*[2,3,4]) # 雖然拆出了 x=2,z=3,但也別想強塞第四個元素給該函式(只定義的三個引數) Traceback (most recent call last): File "<pyshell#48>",4]) TypeError: print_vector() takes 3 positional arguments but 4 were given
上面這兩個錯誤與賦值時的拆解因元素個數不匹配時的錯誤是相對應的
>>> a,b = [1] Traceback (most recent call last): File "<pyshell#54>",in <module> a,b = [1] ValueError: not enough values to unpack (expected 2,got 1) >>> a,3] Traceback (most recent call last): File "<pyshell#55>",3] ValueError: too many values to unpack (expected 2)
當然在賦值時 Python 可以像下面那樣做
a,b,*c = [1,4] >>> print(a,c) 1 2 [3,4]
補充(2020-07-02): 迭代的拆解在 Python 中的術語是 Iterable Unpacking,找到兩個相關的 PEP 448,PEP 3132。在實際上用處還是很大的,比如在拆分字串時只關係自己有興趣的欄位
line = '2020-06-19 22:14:00 2688 abc.json' date,time,size,name = line.split() # 獲得所有欄位值 _,_,name = line.split() # 只對 time 和 name 有興趣 date,*_ = line.split() # 只對第一個 date 有興趣 *_,name = line.split() # 只對最後的 name 有興趣 date,*_,name = line.split() # 對兩邊的 date,name 有興趣
這樣就避免了用索引號來引用拆分後的值,如 split[0],splint[2] 等,有名的變數不容易出錯。注意到 Python 在拆解時非常聰明,它知道怎麼去對應位置,用了星號(*) 的情況,明白如何處理前面跳過多少個,中間跳過多少個,或最後收集多少個元素。
連結:
PEP 448 -- Additional Unpacking Generalizations
PEP 3132 -- Extended Iterable Unpacking
以上就是詳解Python 函式引數的拆解的詳細內容,更多關於python 函式引數拆解的資料請關注我們其它相關文章!