tuple

April 11, 2022

tuple 許多地方都跟 list 很像,它們都是循序類型,只不過 tuple 不可變動。

tuple 操作

tuple 建立後就無法變動,想要建立 tuple,只要在某個值後面加上一個逗號「,」就可以。例如:

>>> 10,
(10,)
>>> 10, 20, 30,
(10, 20, 30)
>>> acct = 1, 'Justin', True
>>> acct
(1, 'Justin', True)
>>> type(acct)
<class 'tuple'>
>>>

建立 tuple 時,最後一個逗號可以省略,雖然只要在值之後加上逗號就可以了,不過,通常會加上 () 讓人一眼就看出這是個 tuple,例如 (1, 2, 3)(1, 'Justin', True),不過要注意,只包含一個元素的 tuple,不能寫成 (elem),而是要寫成 elem, 或者是 (elem,),如果要建立沒有任何元素的 tuple,倒是可以只寫 ()

不可變動的循序類型,都具有以下的行為:

  • x in ss 是否包含x元素。
  • x not in ss 是否未包含x元素。
  • s + t:串接 st
  • s * n:將 s 的元素重複 n 次。
  • s[i]:取得索引 i 處的元素,第一個索引是 0。
  • s[i:j]:切割出從 ij 的元素。
  • s[i:j:k]:切割出從 ij 的元素,每次間隔 k
  • len(s):取得 s 的長度。
  • mix(s):取得 s 中的最小值。
  • max(s):取得 s 中的最大值。
  • s.index(x[, i[, j]]):取第一個 x 的索引位置(可指定從 i 開始,至 j 之前)。
  • s.count(x):取得 x 的出現次數。

tuple 的作用

tuple 可以做什麼呢?有時想要傳回一組相關的值,又不想特地定義一個型態,就會使用 tuple,像是 (1, 'Justin', True),也許就代表了從資料庫中臨時撈出來的一筆資料。

有時希望某函式不要修改傳入的資料,因為 tuple 無法變動,這時就可將資料放在 tuple 傳入,萬一函式的實作者試圖修改資料,執行時就會出錯,也就會知道有人試圖做出規格外的事情。另外,tuple 佔用的記憶體空間比較小。

簡單來說,tuple 可以作為簡便的資料載體(data carrier),因為不可變動,代表著有固定結構,包含資料的欄位順序、長度,而不可變動代表 tuple 記錄了唯一的狀態,沒有變動為其他狀態的可能性,也就是不會有隱藏的狀態。

什麼樣的資料,會需要記錄欄位順序、長度且不可變動呢?

最簡單易懂的例子就是座標點,像是:(10,) 代表一維座標,(20, 30) 代表著二維座標,若是笛卡兒座標系,20 代表 x 座標,30 代表 y 座標;若是極座標系,兩個欄位就分別代表半徑與角度;必要時,也可以使用更多的元素來表示更高維度,例如四元數

也就是說,如果想將資料欄位順序、長度組成,視為一種資料集合(例如笛卡兒座標系的點集合),若不想大費周章地定義一個型態時,就可以使用 tuple

元素拆解

可以將 tuple 中的元素拆解(Unpack),逐一分配給每個變數,例如:

>>> data = (1, 'Justin', True)
>>> id, name, verified = data
>>> id
1
>>> name
'Justin'
>>> verified
True
>>>

記得嗎?tuple() 括號可以省略,雖然多數情況下,為了表示是個 tuple 而寫括號,然而以下情況省略括號,卻是 Python 最常被拿來津津樂道的特性之一:

>>> x = 10
>>> y = 20
>>> x, y = y, x
>>> x
20
>>> y
10
>>>

這個置換(Swap)變數值的動作,在其他語言中,通常需要一個暫存變數,而在 Python 只要一行就可以完成。

拆解元素指定給變數的特性,其實在 listset 等物件上,也可以使用,例如 x, y, z = [1, 2, 3] 的結果,x 會是 1、y 會是 2 而 z 會是 3。

也可以使用 * 來拆解可迭代物件,這特性稱為 Extended Iterable Unpacking,例如:

>>> a, *b = (1, 2, 3, 4, 5)           # 拆解 tuple
>>> a
1
>>> b
[2, 3, 4, 5]
>>> a, *b, c = [1, 2, 3, 4, 5]        # 拆解 list 
>>> a
1
>>> b
[2, 3, 4]
>>> c
5
>>> a, *b, c = range(5)               # 拆解range
>>> a
0
>>> b
[1, 2, 3]
>>> c
4
>>> a, *b = {1, 2, 3}                 # 拆解 set
>>> a
1
>>> b
[2, 3]
>>> a, *b = {'x': 1, 'y': 2, 'z': 3}  # 拆解 dict
>>> a                                 # 取得的是鍵
'x'
>>> b
['y', 'z']
>>>

在某個變數上指定星號「*」,其他變數被分配了單一個值之後,剩餘的元素,就會以 list 指定給標上了星號的變數。

Python 3.5 以後,增加了 Additional Unpacking Generalizations 特性,可以將可迭代物件拆解至 listsettupledict,以 set 為例:

>>> s1 = {'哈', '囉'}
>>> s2 = {'哈', '啦'}
>>> r = {*s1, *s2}
>>> r
{'啦', '囉', '哈'}
>>>

這成為合併 setlisttuple 的另一種方式;雖然 * 也可以應用於 dict,不過僅會拆解出鍵,若想同時拆解出鍵值,可以使用 **,例如:

>>> d1 = {'a': 10, 'b': 20}
>>> d2 = {'b': 30, 'c': 40}
>>> r1 = {*d1, *d2}
>>> r2 = {**d1, **d2}
>>> r1
{'c', 'b', 'a'}
>>> r2
{'a': 10, 'b': 30, 'c': 40}
>>>

其實元素拆解(Unpack)這個特性,是 Python 3.9 以前對〈模式比對(pattern matching)〉的簡易支援;Python 3.10 開始有了 match-case 特性,則是更完善地支援了模式比對。

在 Python 3.5 以後,3.9 之前,** 的這個新特性,可用來簡單地合併多 dict,Python 3.9 以後,如〈字典〉談過的,可以透過 | 來更簡單地合併 dict

分享到 LinkedIn 分享到 Facebook 分享到 Twitter