列表 VS 元組
一個 Python 初學者的普遍疑問:列表與元組的區別是什麼?
答案是:它們之間有兩點不同,並且二者之間有著複雜的相互作用。它們分別是”技術上”的差異和“文化上”的差異。
首先,有一點是相同的:列表與元組都是容器,是一系列的物件。
1 2 3 4 5 6 |
>>> my_list = [1, 2, 3] >>> type(my_list) <class 'list'> >>> my_tuple = (1, 2, 3) >>> type(my_tuple) <class 'tuple'> |
二者都可以包含任意型別的元素甚至可以是一個序列,還可以包含元素的順序(不像集合和字典)。
1 2 3 4 5 6 7 |
>>> my_list[1] = "two" >>> my_list [1, 'two', 3] >>> my_tuple[1] = "two" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment |
現在來講講區別。列表和元組的“技術差異”是,列表是可變的,而元組是不可變的。這是在 Python 語言中二者唯一的差別。
儘管有好幾種表現方式,但這是列表和元組唯一的“技術差異”。比如:列表有一個 append() 的方法來新增更多的元素,而元組卻沒有這個方法:
1 2 3 4 5 6 7 |
>>> my_list.append("four") >>> my_list [1, 'two', 3, 'four'] >>> my_tuple.append("four") Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'tuple' object has no attribute 'append' |
元組並不需要一個 append()
方法,因為元組不能修改。
“文化差異“是指二者在實際使用中的差異:在你有一些不確定長度的相同型別佇列的時候使用列表;在你提前知道元素數量的情況下使用元組,因為元素的位置很重要。
舉個例子,假設你有一個函式是用來在目錄中查詢結尾為 *.py 的檔案。函式返回的應該是一個列表,因為你並不知道你會找到多少個檔案,而這些檔案都有相同的含義:它們僅僅是你找到的另一個檔案。
1 2 |
>>> find_files("*.py") ["control.py", "config.py", "cmdline.py", "backward.py"] |
另一方面,讓我們假設你需要儲存五個值來代表氣象觀測站的位置:id ,城市,國家,緯度,經度。元組就比列表更適用於這種情況:
1 2 3 |
>>> denver = (44, "Denver", "CO", 40, 105) >>> denver[1] 'Denver' |
(讓我們暫時不要討論使用類來表示的這種情況)。第一個元素是 id ,第二個元素是城市,等等。位置決定了它的意義。
把這種“文化差異”放到 C 語言來講,列表像是陣列,元組則像是 structs 結構體。
Python 採用了命名元組的方法來使含義更加明確:
1 2 3 4 5 6 7 8 9 |
>>> from collections import namedtuple >>> Station = namedtuple("Station", "id, city, state, lat, long") >>> denver = Station(44, "Denver", "CO", 40, 105) >>> denver Station(id=44, city='Denver', state='CO', lat=40, long=105) >>> denver.city 'Denver' >>> denver[1] 'Denver' |
對元組和列表之間“文化差異”的一個總結是:元組是不需要名字的命名元組。
“技術差異”和“文化差異”這種區分也並不準確,因為有時候他們並不穩定。為什麼同種元素的序列可變,而不同元素的序列不可變呢?舉個例子,我不能修改我的氣象站,因為命名元組是不可變的元組。
1 2 3 4 |
>>> denver.lat = 39.7392 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute |
有時技術考量要比文化考量重要。比如你不能把列表當做字典的關鍵字,因為只有不可變的值才能進行雜湊運算,因此只有不可變的值才能作為關鍵字。要使用列表做關鍵字,你需要把它轉化為元組:
1 2 3 4 5 6 7 8 9 |
>>> d = {} >>> nums = [1, 2, 3] >>> d[nums] = "hello" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' >>> d[tuple(nums)] = "hello" >>> d {(1, 2, 3): 'hello'} |
技術和文化的另一個衝突是:在 Python 中元組使用的地方,列表也起著重要作用。當你定義一個帶參函式,元組作為引數傳入,即使這個值的位置並不顯著,至少 Python 知道這是一個元組。你可能會說這是個元組因為你不能改變被傳進來的值,但這只是重視了技術上的差異而非文化上的差異。
我知道:在引數陣列中,位置很重要,因為他們是位置引數。但是在函式中,它會接收引數陣列並傳入另一個函式,它僅僅是一組引數,與其他引數並無不同。其中一些會在呼叫中變化。
Python 之所以在這裡使用元組是因為元組比列表更節省空間。列表被重複分配使得在新增元素上更快。這體現了 Python 的實用性:相比於在引數陣列上,列表/元組的語義差別,在這種情況下,使用這種資料結構更好。
大部分情況下,你應該根據文化差異來選擇使用列表還是元組。思考下你資料的含義。如果實際上根據你的程式來計算,你的資料長度並不固定,那麼可能用列表更好。如果在寫程式碼時你知道第三個元素的含義,那麼用元組更好。
另一方面,函數語言程式設計強調使用不可變的資料結構來避免產生使程式碼變得更難解讀的副作用。如果你喜歡使用函數語言程式設計,那麼你可能因為元組的不可變而更喜歡它們。
那麼:你應該用元組還是列表?答案是:永遠沒有一個簡單的答案。