[Python] *args 和 **kwargs 是什麼?一次搞懂它們!
在翻閱 Python 的函式庫時常常會看到定義參數的地方放了 *args 和 **kwargs 這樣的東西,這究竟是什麼呢?讓我們先談談函式參數的定義。
- 預設參數
一般的定義方法就不多說了,直接來看有預設值的參數:
def plus(a, b, c=None):
res = a + b + (c if c else 0)
return res
預設參數的用處通常是實作函式重載用的,可以使一個函式在接受引數時更有彈性,而要注意的語法問題是:預設參數在函式定義時一定要放在非預設參數的後面。
但如果我們想實作無限版的 plus() 函式呢?總不可能一直增加預設參數吧! 這時候我們可以用「*」來將引數收集到一個 tuple 中。
但如果我們想實作無限版的 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 參數來更改最後的換行字元。像這樣不用理會參數的真正順序,而只要給定名字然後指定值的情況,就是在使用關鍵字引數。
如果我們要指定的參數太多而造成版面不簡潔的話,可以考慮使用「**」來拆解一個裝有參數名與值的 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 呢?可以如下:(2019/7/15 補:將這部份的程式碼改得更精簡)
def fun(**settings):
settings.setdefault('name', 'Hello')
settings.setdefault('attack', 50)
settings.setdefault('defense', 0)
settings.setdefault('hp', 150)
print(settings)
fun(name='Sky', attack=100, hp=500)
# {'name': 'Sky', 'attack': 100, 'defense': 0, 'hp': 500}
注意第 2~5 行,我們還可以順便給定預設值,這不就跟一開始的預設參數一樣了嗎?
- 集大成- * 與 ** 雙管齊下
* 和 ** 都很方便,但用了 * 就不能指名;而用了 ** 就一定要指名,好像有點美中不足。其實我們可以將這兩個合併起來使用,就如同我們常看到的一樣,可以接受任意引數:
def fun(*args, **kwargs):
print(args, kwargs, sep='\n')
唯一要注意的是,* 一定要在 ** 的前面,而呼叫函式時有名字的也一定要在沒名字的後面。這種集大成的寫法通常會在裝飾器時使用,讓裝飾器可以接受參數數量不同的函式。
- 再談 * 的其它用法
另外,在 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)一定要以關鍵字引數(指名)傳入
這個寫法可以限制使用者一定要指名傳入引數,而不是依賴原本的順序。- 超級集大成
def fun(a, *args, kw1, **kwargs):
print(a, args, kw1, kwargs, sep=' # ')
fun(1, 2, 3, 4, 5, kw1=6, g=7, f=8, l=9)
# 1 # (2, 3, 4, 5) # 6 # {'g': 7, 'f': 8, 'l': 9}
可以看到這裡的 *args 同時扮演了原本和分隔的角色。好啦,我覺得這個部分可能已經不是像我這樣的新手能好好利用的了,所以就僅止於介紹而已。
這次就到這邊了!謝謝大家的閱讀m(_ _)m,如有疑慮或指正歡迎留言提出。
參考資料:
1) stackoverflow 中的相關問題
後記:
其實那個什麼「唯-關鍵字引數」是我亂翻的XDD(看也知道
另外我也終於把這個 * 的用法給搞懂了~ 希望這篇文不會太難懂
好文~
回覆刪除想請問你的blogspot樣式是怎麼調的?
也想用用這種形式
先謝謝你的讚賞!
刪除如果是指整個blogger的主題的話,可以到 Blogger後台 > 主題,就會看到下面有不少的範本可以隨意取用,若是想要微調特色之類的話可以在剛剛那個頁面選擇「自訂」或「編輯HTML」。
至於程式碼的部分,可以到這個網站 : https://prismjs.com/ 選擇你喜歡的主題。
:)
感謝分享!! 終於看懂怎麼用了QQ
回覆刪除喜歡您的解說!! 大感謝!!
回覆刪除清晰易懂喔~ 感謝你的分享
回覆刪除超讚的解說owob
回覆刪除寫得很清楚!!
回覆刪除不過 *args 不算是放入 tuple
比較類似 iterator
謝謝~
刪除不過 *args 的話是 tuple 沒錯喔,python 的 language reference 有寫說是放到 tuple 裡:
`If the form “*identifier” is present, it is initialized to a tuple receiving any excess positional parameters, defaulting to the empty tuple.`
-- 8.7. Function definitions (https://docs.python.org/3/reference/compound_stmts.html#function-definitions)
另外,實際測試也是這樣:
>>> def f(*args):
... print(type(args))
...
>>> f()
>>> f(3, 4, 5)
>>>
啊 角括號被過濾掉了
刪除最後的兩行程式都會印出 class 'tuple'