在翻閱 Python 的函式庫時常常會看到定義參數的地方放了 *args
和 **kwargs
這樣的東西,這究竟是什麼呢?讓我們先談談函式參數的定義。
函式參數語法基礎
預設參數
一般的定義方法就不多說了,直接來看有預設值的參數:
def plus(a, b, c=None):
res = a + b + (c if c else 0)
return res
預設參數的用處通常是實作函式重載用的,可以使一個函式在接受引數時更有彈性,而要注意的語法問題是:預設參數在函式定義時一定要放在非預設參數的後面。
但如果我們想實作無限版的 plus()
函式呢?總不可能一直增加預設參數吧! 這時候我們可以用「*
」來將引數收集到一個 tuple 中。
*
- 收集至 Tuple
先來看看範例:
def plus(*nums):
res = 0
for i in nums:
res += i
return res
透過 *
收集的引數會被放到一個 tuple 中,所以我們可以使用 for 來對它進行迭代。
這樣就可以理解為什麼要使用 *args
這個參數了,但是 **kwargs
又是什麼呢?我們要先從關鍵字引數來說起:
關鍵字引數 Keyword Argument
在呼叫 print()
時,我們有時會指定 sep 參數做為分隔輸出的字元,或是使用 end 參數來更改最後的換行字元。像這樣不用理會參數的真正順序,而只要給定名字然後指定值的情況,就是在使用關鍵字引數。
若是直接傳入函式而沒有寫明參數名的話,則稱為位置引數(Positional Argument)。
如果我們要指定的參數太多而造成版面不簡潔的話,可以考慮使用「**」來拆解一個裝有參數名與值的 dict。
**
第一招 - 拆解 Dict
原諒我使用這麼中二的小標題XDD
直接看實例應該就能懂了:
dt = {'sep': ' # ', 'end': '\n\n'}
print('hello', 'world', **dt)
# 等同於 print('hello', 'world', sep=' # ', end='\n\n')
雖然這不算真的發揮到 **
的長處,因為我們要指定的參數不多,但就足以展現他的功用了。
上面是在處理呼叫時引數太多的問題,但如果在定義函式時,參數就太多了呢?
**
第二招 - 收集至 Dict
雖然我們可以用上面的單星號來收集到一個 tuple 中,但這樣很難知道第幾個元素代表什麼、也無法隨心所欲的選擇參數傳入了。這時我們就可以再次利用 **
以及 dict「具名」的性質來定義函式:
def fun(**_settings):
print(_settings)
fun(name='Sky', attack=100, hp=500)
# {'name': 'Sky', 'attack': 100, 'hp': 500}
可以看到,傳入的引數被收集成一個 dict 了,那我們要怎麼利用這個 dict 呢?可以如下:
def fun(**settings):
settings.setdefault('attack', 50)
settings.setdefault('defense', 0)
print(settings)
fun(name='Sky', attack=100, hp=500)
# {'name': 'Sky', 'attack': 100, 'defense': 0, 'hp': 500}
注意第 2、3 行,我們還可以順便給定預設值,這不就跟一開始的預設參數一樣了嗎?
集大成- *
與 **
雙管齊下
*
和 **
都很方便,但用了 *
就不能指名;而用了 **
就一定要指名,好像有點美中不足。其實我們可以將這兩個合併起來使用,就如同我們常看到的一樣,可以接受任意引數:
def fun(*args, **kwargs):
print(args, kwargs, sep='\n')
唯一要注意的是,*
一定要在 **
的前面,而呼叫函式時有名字的也一定要在沒名字的後面。這種集大成的寫法通常會在裝飾器時使用,讓裝飾器可以接受參數數量不同的函式。
再談 *
的其它用法
我們可以在傳入引數時使用 **
來拆解 dict,那就不能用 *
來拆解 tuple 嗎?其實是可以的,只是我覺得這個沒那麼難理解,就沒有寫出來了。
另外,在 Python 3 裡,可以在定義函式時使用單獨的 *
來做為非指名參數和指名參數(唯-關鍵字引數,Keyword-Only Arguments)的區隔,底下這個範例結合了本文最上面的預設參數:
def fun(a, b=20, *, kw1, kw2=40):
print(a, b, kw1, kw2)
fun(1, 2, kw1=3, kw2=4) # 1 2 3 4
fun(10, kw1=30) # 10 20 30 40
# 在傳入引數時,在 * 後面的(kw1 和 kw2)一定要以關鍵字引數(指名)傳入
這個寫法可以限制使用者一定要指名傳入引數,而不是依賴原本的順序。
在 Python 3.8 中,引入了唯-位置參數(Positional-only Parameters),
使用方式是以一個斜線 /
來表示在這之前的參數都不能指名,而只能依靠位置來傳入。
有興趣的人可以參考官方文件及 PEP 570
超級集大成
我們可以將 *args
、分隔用的 *
、以及 **kwargs
一起使用:
def fun(a, *args, kw1, **kwargs):
print(a, args, kw1, kwargs, sep=' # ')
fun(1, 2, 3, 4, 5, kw1=6, g=7, f=8, m=9)
# 1 # (2, 3, 4, 5) # 6 # {'g': 7, 'f': 8, 'm': 9}
可以看到這裡的 *args
同時扮演了原本和分隔的角色。
好啦,我覺得這個部分可能已經不是像我這樣的新手能好好利用的了,所以就僅止於介紹而已。
這次就到這邊了!謝謝大家的閱讀 m(_ _)m
,如有疑慮或指正歡迎留言提出。
參考資料:
後記:
其實那個什麼「唯-關鍵字引數」是我亂翻的XDD(看也知道
另外我也終於把這個 *
的用法給搞懂了~ 希望這篇文不會太難懂