Python 單雙星號(* & **)解說

Posted on  Sep 19, 2022  in  Python 程式設計 - 初階  by  Amo Chen  ‐ 4 min read

Python 內將函數的參數分為 keyword argument 以及 positional argument, 這 2 者的差異在官方文件中有清楚說明,同時這 2 種分別支援單星號 * 與雙星號 ** 的用法,是 Python 開發者一定要熟知的用法。

本文將詳細介紹說明。

本文環境

  • Python 3.7

Keyword argument

An argument preceded by an identifier (e.g. name=) in a function call or passed as a value in a dictionary preceded by **.

Keyword argument 在呼叫函數/方法時,需要明確指定參數名稱。

例如以下透過 keyword argument 方式呼叫內建的 complex() 類別時(該類別接受 2 個參數,分別是 realimag ),分別明確指定參數值 real=3, imag=5

complex(real=3, imag=5)

除了上述明確指定參數值的方式之外, keyword argument 也支援以 ** 2個星號加在字典(dictionary)型態的值前面進行呼叫, Python 就會自動將該字典裡的 key 作為參數名稱, value 作為參數值進行呼叫:

kw = {'real': 3, 'imag': 5}
complex(**kw)

上述範例也等同於下列形式:

complex(**{'real': 3, 'imag': 5})

以 keyword argument 方式呼叫函式/方法,可以無視參數的次序,可讀性也較佳,例如以下範例的結果都是相同的:

complex(real=3, imag=5)  # (3+5j)
complex(imag=5, real=3)  # (3+5j)

順帶一提, **{'real': 3, 'imag': 5} 的寫法,稱為 unpacking, 可以用來合併多個 dictionary, 例如以下範例:

a = {
	'a': 0,
}

ab = {
	'a': 1,
	'b': 2,
}

c = {
	'c': 3,
	**a,
	**ab,
}

print(c)
# 輸出結果:
# {'c': 3, 'a': 1, 'b': 2}

從上述範例的輸出結果可以發現如果用 unpacking 進行合併,如果有相同的 key 存在,將以後出現的值為準,這也是為何 a: 0 最後會變為 a: 1 的原因。

Positional argument

An argument that is not a keyword argument. Positional arguments can appear at the beginning of an argument list and/or be passed as elements of an iterable preceded by *.

Positional argument 與 Keyword argument 不同,不需要明確指定參數名稱,而是以參數值的位置,將參數一對一帶入,例如 complex() 函數定義接受 2 個參數 realimag

class complex([real[, imag]])

若以 Positional argument 方式進行呼叫, Python 會自動將參數值 3, 5 按照位置一對一地賦予 realimag , 所以 real 會得到 3, imag 會得到 5:

complex(3, 5)

所以用 Positional argument 呼叫時的參數值帶入順序相當重要,如果弄錯就可能有 bug 產生,例如以下 2 者結果就會不一樣:

complex(3, 5)   # (3+5j)
complex(5, 3)   # (5+3j)

Positional argument 支援以 * 1 個星號加在 iterable 型態的值前面進行呼叫,例如:

args = (3, 5)
complex(*args)

上述範例也等同於下列形式:

complex(*(3, 5))

值得注意的是 * 可以用在任何 iterable 型態的值,包含 List, String, Generator 等等,因此 complex(*(3, 5)) 也等同於下列形式:

complex(*[3, 5])

另外星號也可以與一般 positional argument 用法混用,例如下列函式:

def func(p1, p2, p3):
    print(f"p1={p1}, p2={p2}, p3={p3}")

呼叫時可以部分參數值以 * 代替:

func(1, *[2, 3])

# 輸出結果:
# p1=1, p2=2, p3=3

順帶一提, *[3, 4] 的寫法,也稱為 unpacking, 可以用來 unpacking iterable, 例如以下範例用單星號 unpacking 2 個 list 進行合併:

a = [1, 2]
b = [3, 4]
c = [*a, *b]

print(c)

# 輸出結果:
# [1, 2, 3, 4]

單星號 * 在函式定義上的應用

If the form “*identifier” is present, it is initialized to a tuple receiving any excess positional parameters, defaulting to the empty tuple.

如果想要像 print() 一樣接受不定長度的 positional arguments, 例如:

print(1, 2, 3, 4, 5, 6, 7, 8, 9)

就可以在函式定義使用 * 加上變數名稱, Python 會建立一個 tuple, 然後將所有的 positional 參數值都存進去該 tuple, 例如以下定義的函式 func 以 *args 將 args 變成接受不定長度的參數,最後可以從 func(1, 2, 3) 執行結果發現參數 1, 2, 3 被存進 args ,變成值為 (1, 2, 3) 的 tuple:

def func(*args):
    print(args, type(args))

func(1, 2, 3)

# 輸出結果:
# (1, 2, 3) <class 'tuple'>

如果進一步對上述 func 函式帶入 keyword arguments 例如 func(1, 2, 3, a=4) 將會出現以下錯誤:

Traceback (most recent call last):
  File "functest.py", line 4, in <module>
    func(1, 2, 3, a=4)
TypeError: func() got an unexpected keyword argument 'a'

這是由於 def func(*args) 的寫法代表只接受 positional arguments.

雙星號 ** 在函式定義上的應用

If the form “**identifier” is present, it is initialized to a new ordered mapping receiving any excess keyword arguments, defaulting to a new empty mapping of the same type.

如果想要像 dict() 一樣接受不定長度的 keyword arguments, 例如:

d = dict(a=1, b=2, c=3, d=4, e=5, f=6)

則可以在函式定義使用 ** 加上變數名稱, Python 會建立一個 dictionary, 然後將所有的 keyword 參數名稱與參數值都存進去該 dictionary, 例如以下定義的函式 func 以 **kwargs 將 kwargs 變成接受不定長度的 keyword 參數,最後可以從 func(a=1, b=2, c=3) 執行結果發現參數 a=1, b=2, c=3 被存進 kwargs ,變成值為 {'a': 1, 'b': 2, 'c': 3} 的 dictionary:

def func(**kwargs):
    print(kwargs, type(kwargs))

func(a=1, b=2, c=3)

# 輸出結果:
# {'a': 1, 'b': 2, 'c': 3} <class 'dict'>

如果進一步對上述 func 函式帶入 positional arguments 例如 func(4, 5, a=1, b=2, c=3) 將會出現以下錯誤:

Traceback (most recent call last):
  File "functest.py", line 4, in <module>
    func(4, 5, a=1, b=2, c=3)
TypeError: func() takes 0 positional arguments but 2 were given

這是由於 def func(**kwargs) 的寫法代表只接受 keyword arguments.

當然,上述 2 種關於星號的用法也能夠一起使用:

def func(*args, **kwargs):
    print(args, type(args))
    print(kwargs, type(kwargs))


func(1, 2, 3, a=4, b=5, c=6)

# 輸出結果:
# (1, 2, 3) <class 'tuple'>
# {'a': 4, 'b': 5, 'c': 6} <class 'dict'>

Parameters after “*” or “*identifier” are keyword-only parameters and may only be passed by keyword arguments.

不過單星號 * 一定要在雙星號 ** 前面,否則就會出現以下錯誤:

File "functest.py", line 1
    def func(**kwargs, *args):
                       ^
SyntaxError: invalid syntax

以上就是關於 Python 單雙星號的介紹, Happy coding!

References

https://docs.python.org/3/glossary.html

https://docs.python.org/3/reference/expressions.html

https://docs.python.org/3/reference/expressions.html#calls

https://docs.python.org/3/faq/programming.html#what-is-the-difference-between-arguments-and-parameters

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

追蹤新知

看完這篇文章了嗎?還意猶未盡的話,追蹤粉絲專頁吧!

我們每天至少分享 1 篇文章/新聞或者實用的軟體/工具,讓你輕鬆增廣見聞提升專業能力!如果你喜歡我們的文章,或是想了解更多特定主題的教學,歡迎到我們的粉絲專頁按讚、留言讓我們知道。你的鼓勵,是我們的原力!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!