【Python】詳解 單星號操作符 * 與 雙星號操作符 **

目錄

一、緒論

二、說明

2.1 算術運算符 —— 乘與乘方

2.2 字符串運算符 —— 重複

2.3 函數實參彙集 —— 打包

2.4 函數實參分散 —— 解包

2.5 解壓可迭代對象賦值給多個變量


一、緒論

在 Python 中,單星號 * 和雙星號 ** 這兩個符號存在多種用法,平時不注意歸納或很少使用則容易混淆或遺忘。爲此,本文將根據各類書籍、文檔等,詳細梳理各類用法並予以說明。

二、說明

2.1 算術運算符 —— 乘與乘方

* 爲乘法,** 爲乘方/乘冪

>>> 2 * 4
8
>>> 2 ** 4
16

2.2 字符串運算符 —— 重複

使用乘號 * 可重複輸出 string (由於 string 是不可變的,* 的本質是創建新的 string):

>>> 'cs' * 2
'cscs'
>>> me = 'go'
>>> me * 3
'gogogo'

2.3 函數實參彙集 —— 打包

*args **kwargs 常作爲 魔法變量 出現於函數定義中,用於將不定量實參傳遞給函數。其中:

*args 的本質是將 位置實參 彙集爲 tuple 然後由變量 args 接收,:

>>> def test1(x, *args):
	print(x, args)

>>> test1('a', 'b', 'c', 'd')
a ('b', 'c', 'd')
>>> test1(1, 2, 3, 4)
1 (2, 3, 4)

**kwargs 的本質則是將 關鍵字實參 彙集爲 dict 然後由變量 kwargs 接收:

>>> def test2(**kwargs):
	for key, value in kwargs.items():
		print("{0} = {1}".format(key, value))

>>> test2(a=1, b=2, c=3, d=4)
a = 1
b = 2
c = 3
d = 4

單星操作符 * 無法彙集關鍵字參數,而雙星操作符 ** 可以。若想在函數定義時同時使用 位置形參 + 單星操作符 * + 雙星操作符 ** ,則須按照如下順序:

>>> def test3(z, *args, **kwargs):
	print(z, args, kwargs)

>>> test3(0, 1, 2, 3, 4, a=1, b=2, c=3, d=4)
0 (1, 2, 3, 4) {'a': 1, 'b': 2, 'c': 3, 'd': 4}

此外,寫成 *args **kwargs 其實並非必須的,僅爲通俗的命名約定,只有變量前的單星號 * 或 雙星號 ** 纔是必須的。若有意,還可寫成 *var 和 **vars 等等。

2.4 函數實參分散 —— 解包

若函數形參是定長參數,則可使用 分散操作符 (scatter operator) * 或 ** 初始化函數,類似解引用 tuple 和 dict 。例如:

使用 分散操作符 * 對 tuple 實參解包:

>>> def test4(var1, var2, var3, var4):
	print(var1, var2, var3, var4)

>>> variables = (1, 2, 3, 4)  # 注意, 可迭代對象即可, 不限於 tuple

>>> test4(variables)   # 不解包要報錯
Traceback (most recent call last):
  File "<pyshell#36>", line 1, in <module>
    test4(variables)
TypeError: test4() missing 3 required positional arguments: 'var2', 'var3', and 'var4'

>>> test4(*variables)  # 解包饋入即可
1 2 3 4

使用 分散操作符 ** 對 dict 實參解包:

>>> def test4(var1, var2, var3, var4):
	print(var1, var2, var3, var4)

>>> variables2 = {'var1':5, 'var2':6, 'var3':7, 'var4':8}  # 注意 key 對應關鍵字形參

>>> test4(variables2)    #  不解包要報錯
Traceback (most recent call last):
  File "<pyshell#48>", line 1, in <module>
    test4(variables2)
TypeError: test4() missing 3 required positional arguments: 'var2', 'var3', and 'var4'

>>> test4(**variables2)  # 解包饋入即可
5 6 7 8

2.5 解壓可迭代對象賦值給多個變量

使用星號操作符 * 構成星號表達式能夠實現對 可迭代對象 iterable 解壓,從而將不確定個數或任意個數元素的可迭代對象 iterable 賦值給任意數量的變量。其中,使用 * 的變量將接受多餘參數,並總保存在 list 中。

注意,可迭代對象 iterable 包含:序列 sequence (list、string、tuple)、set、dict、迭代器 iterator、生成器 generator、文件對象 等,而不侷限於 2.4 節中的類型。有些博客、教程聲稱“使用 * 可以對序列解壓”是不盡然的,不確切的。

>>> x, *y = [1, 2, 3, 4, 5, 6]  # unpack list 
>>> x
1
>>> y
[2, 3, 4, 5, 6]
# -----------------------------------------------------------------------------
>>> x, *y = 'abcdefg'  # unpack string
>>> x
'a'
>>> y
['b', 'c', 'd', 'e', 'f', 'g']
# -----------------------------------------------------------------------------
>>> x, *y = (1, 2, 3, 4, 5, 6)  # unpack tuple
>>> x
1
>>> y
[2, 3, 4, 5, 6]
# -----------------------------------------------------------------------------
>>> x, *y = {'a':1, 'b':2, 'c':3}  # unpack dict
>>> x
'a'
>>> y
['b', 'c']
# -----------------------------------------------------------------------------
>>> x, *y = {1, 2, 3, 4, 5, 6}  # unpack set
>>> x
1
>>> y
[2, 3, 4, 5, 6]
# -----------------------------------------------------------------------------
>>> generator = (i for i in range(1,7))
>>> generator
<generator object <genexpr> at 0x00000212A48F75C8>
>>> x, *y = generator  # unpack for generator
>>> x
1
>>> y
[2, 3, 4, 5, 6]

補個科普:維基百科解釋,在 Python 中,迭代器是遵循迭代協議的對象。使用 iter() 從任何序列對象中得到迭代器 (如 list, tuple, dict, set 等)。另一種形式的輸入迭代器是生成器 generator。很多容器如 list、string 可用 for 循環遍歷對象。for 語句會調用容器對象中的 iter() 函數,該函數返回一個定義了 __next__() 方法的迭代器對象,該方法將逐一訪問容器中的元素。故在 Python中,任意對象只要定義了__next__方法,就是一個迭代器。因此,Python 容器如 list、string、tuple、set、dict 實際上都可以被稱作迭代器 iterator。

此外,星號* 對於解壓、分割 string 很有用:

>>> line = 'nobody:*:-4:-4:Unprivileged User:/var/empty:/usr/bin/true'
>>> uname, *fields, homedir, sh = line.split(':')  # string 分割 + 解包
>>> uname
'nobody'
>>> homedir
'/var/empty'
>>> sh
'/usr/bin/true'
# -----------------------------------------------------------------------------
>>> record = ('Child', 66, 12.34, (1, 6, 2020))
>>> name, *_, (*_, year) = record  # 使用佔位符如 _ 來接收無用的部分
>>> name
'Child'
>>> year
2020

如果夠聰明,還能用這種分割語法巧妙實現遞歸算法:

>>> items = [1, 10, 7, 4, 5, 9]
>>> def new_sum(items):  # 其實因語言層面限制, 遞歸非 Python 所長
	head, *tail = items
	return head + sum(tail) if tail else head
>>> new_sum(items)
36

參考文獻:

《Python Cookbook》、《Intermediate Python》、《Think Python》

https://www.runoob.com/python3/python3-basic-operators.html

https://www.runoob.com/w3cnote/python-one-and-two-star.html

https://www.jianshu.com/p/2acee6392be5

https://zhuanlan.zhihu.com/p/76831058

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章