python 的 tuple 是不是冗餘設計?
- 2019 年 10 月 6 日
- 筆記

覺得這個問題挺好,值得好好思考。

Tuple類型對於Python自身來說是非常重要的數據類型,比如說函數調用,實際上會將順序傳入的參數先組成一個tuple;多返回值也是靠返回一個tuple來實現的。因為太常用,所以需要一個更有效的數據結構來提高效率,一個不可變的tuple對象從實現上來說可以比list簡單不少。再比如說code對象會記錄自己的參數名稱列表,free variable名稱列表等等,這些如果用list,就可能被從外部修改,這樣可能導致解釋器崩潰;那就只能選擇改成一個函數每次都返回一個新的列表,這樣又很浪費。所以即使是從解釋器自身實現的角度上來說引入這樣一個不可變的序列類型也是很重要的。
對程式設計師來說如果沒有什麼美學上的追求的話,tuple最大的便利在於它是一個hashable的類型,而且hash演算法與值直接對應,這樣在Python里很容易用多個值的組合來做key生成一個dict,比如說我們網路里有20台交換機,每個交換機有24個口,那要唯一標識每個口就需要用(交換機ID,口編號),這個tuple可以做dict的key的話,編寫程式起來就很方便了。
Immutable

首先說說Immutable的優勢:
- 為什麼FP在多核時代重獲重視?一個很重要的原因就是FP的Immutable特性。Immutable類型不存在Mutable類型的同步問題;
- 因為不可變,Immutable類型的記憶體結構設計就少了很多假設性條件,帶來的直接好處就是性能優化;
- Python里只有Immutable類型是Hashable的,因為同樣是Immutable使得Hash Table的設計來得簡單;
- 業務上不該改變的就不允許其發生中途變化!

immutable的好處實在是太多了:
性能優化,多執行緒安全,不需要鎖,不擔心被惡意修改或者不小心修改。
Tuple的使用場景
List跟Tuple使用場景上的一點主要區別
看到好多Python程式設計師都喜歡第一時間就用List,不管合不合適(當然有時候是需要可修改的):
[['張三', 35], ['陳八', 28]]
List存放的數據應該是同質數據;而Tuple呢?其存儲的應該是像資料庫記錄這樣的結構化數據——這個區別是List和Tuple使用上最直白的區別。
所以上述程式碼應該改為:
[('張三', 35), ('陳八', 28)] Tuple是Hashable的
這可以應用在一些有趣的場景,比如把一些「記錄」作為Key:

Out[11]:
[(('張三', 1995), '√ 少林長拳'), (('張三', 1999), '√ 武當太極'), (('張三', 2015), '√ 破空神掌'), (('李七', 2007), '√ 大摔碑手'), (('李七', 2017), '√ 長空劍法')] 一個武者習武履歷記錄的時間軸就出來了。
此外,其實Python中大量運用Tuple。好比上圖程式碼里,在sorted中指定排序順序的欄位。然後再看看person.items(),其結構類似上面的輸出,裡面同樣藏著Tuple結構。
比如還有print:
In [15]: print("%s is %s." % ('Foo', 'Bar')) Foo is Bar. Tuple解構
在上面的print里,其實就是Tuple解構。
比如:
In [28]: name, year = ('張三', 1995) In [29]: name Out[29]: '張三' In [30]: year Out[30]: 1995 In [31]: for (name, year), gongfu in sorted(person.items(), key=lambda item: (item[0], item[1])): ...: print('{n} -- {y} -- {gf}'.format(n=name, y=year, gf=gongfu)) ...: 張三 -- 1995 -- √ 少林長拳 張三 -- 1999 -- √ 武當太極 張三 -- 2015 -- √ 破空神掌 李七 -- 2007 -- √ 大摔碑手 李七 -- 2017 -- √ 長空劍法 Tuple解構特性對於函數返回多值是非常有意義的。
collections.namedtuple具名元組
附帶提提collections.namedtuple,一個工廠函數,其在官方文檔中的定義是:
factory function for creating tuple subclasses with named fields. Returns a new tuple subclass named typename. The new subclass is used to create tuple-like objects that have fields accessible by attribute lookup as well as being indexable and iterable...
In [57]: from collections import namedtuple ...: ...: GF = namedtuple('GongFu', 'name, gf, pai, props') ...: data = [ ...: GF('張三丰', '太極拳', '武當派', ('男', 60)), ...: GF('劉小七', '八卦掌', '八卦門', ('男', 41)), ...: GF('石破天', '無影手', '無影門', ('男', 39)) ...: ] ...: for item in data: ...: print(f'{item.name} -- {item.gf} -- {item.pai} -- {item.props[0]} -- {item.props[1]}.') ...: 張三丰 -- 太極拳 -- 武當派 -- 男 -- 60. 劉小七 -- 八卦掌 -- 八卦門 -- 男 -- 41. 石破天 -- 無影手 -- 無影門 -- 男 -- 39. collections.namedtuple可以帶上名稱屬性,在邏輯及調試上更加清晰。在作為「記錄」使用時,無疑collections.namedtuple更為合適。而且collections.namedtuple的構建在記憶體上跟Tuple是一樣的,從而也足夠優化。
而且collections.namedtuple也能夠解構:
In [65]: name, gf, pai, (props0, props1) = data[2] In [66]: name, gf, pai, props0, props1 Out[66]: ('石破天', '無影手', '無影門', '男', 39) 番外篇
Tuple的一個定義:

Tuple其實是在大量程式語言中得以大量使用的。在一些FP語言中Tuple的基礎其實是Pair,比如Idris中,("Baz", "Foo", "Bar", 39)被當成("Baz", ("Foo", ("Bar", 39)))。