面試中,會被問到一個題目是,List與Tuple的不一樣,以及爲何須要二者。今天就把這個知識點整理一下。python
List和Tuple,即列表和元組,都是一個能夠放置任意數據類型的有序集合。不一樣的是,列表是可變的(mutable),元組是不可變的(immutable)(關於二者差別,咱們放到下一節詳細說明)。面試
l = [1,2,3,4]
1[-1]
4
t = (1,2,3,4)
t[-1]
4
複製代碼
l = [1,2,3,4]
l[1:3]
[2, 3]
t = (1,2,3,4)
t[1:3]
(2, 3)
複製代碼
l = [[1, 2, 3], [4, 5]] # 列表的每個元素也是一個列表
t = ((1, 2, 3), (4, 5, 6)) # 元組的每個元素也是一個元組
list((1,2,3))
[1,2,3]
tuple([1,2,3])
(1,2,3)
複製代碼
l = [3, 2, 3, 7, 8, 1]
l.count(3)
2
l.index(7)
3
l.reverse()
l
[1, 8, 7, 3, 2, 3]
l.sort()
l
[1, 2, 3, 3, 7, 8]
tup = (3, 2, 3, 7, 8, 1)
tup.count(3)
2
tup.index(7)
3
list(reversed(tup))
[1, 8, 7, 3, 2, 3]
sorted(tup)
[1, 2, 3, 3, 7, 8]
複製代碼
注意,list.reverse() 和 list.sort() 分別表示原地倒轉列表和排序,但元組沒有內置的這兩個函數)。reversed() 和 sorted() 一樣表示對列表 / 元組進行倒轉和排序,可是會返回一個倒轉後或者排好序的新的列表 / 元組。緩存
上一小節,咱們簡單回顧了一下列表和元組的基礎,這一小節,咱們來來細品列表和元組的不一樣。app
一言以蔽之,核心不一樣即,列表是可變對象(mutable),而元組是不可變對象(immutable)。正由於如此,才致使了二者從三個方面有些差別。函數
一般來講,由於有垃圾回收機制的存在,若是一些變量不被使用了,Python會回收它們所佔用的內存,返還給操做系統,以便其餘變量或其餘應用使用。但對於一些不可變對象,好比元組,若是它不被使用而且佔用空間不大時,Python會暫時緩存這部份內容,這樣下次再建立一樣大小的元組時,Python就能夠不須要向操做系統發出請求,尋找內存,而是直接分配以前緩存的內容空間。這樣能大大加快程序的運行速度。oop
咱們能夠用python3 -m timeit -s '<operation>'
來觀察一下具體操做的耗時狀況。性能
python3 -m timeit 'a=[1,2,3,4,5,6,7,8,9,10]'
2000000 loops, best of 5: 107 nsec per loop
python3 -m timeit 'b=(1,2,3,4,5,6,7,8,9,10)'
20000000 loops, best of 5: 17.5 nsec per loop
複製代碼
能夠看到,元組初始化耗時,大概是列表的6倍。在初始化方面,元組性能勝。優化
python3 -m timeit -s 'a=[1,2,3,4,5,6,7,8,9,10]' -s 'a[3]'
20000000 loops, best of 5: 11.4 nsec per loop
python3 -m timeit -s 'b=(1,2,3,4,5,6,7,8,9,10)' -s 'b[3]'
20000000 loops, best of 5: 11.4 nsec per loop
複製代碼
差異不大,平手。spa
python3 -m timeit \
-s "L = []" \
-s "x = range(10000)" \
"for item in x:" " L.append(item)"
200 loops, best of 5: 1.14 msec per loop
python3 -m timeit \
-s "T = ()" \
-s "x = range(10000)" \
"for item in x:" " T += (item,)"
2 loops, best of 5: 400 msec per loop
複製代碼
能夠看到,在追加時,若是咱們用元組,耗時大概是列表追加的350倍。由於元組是不可變對象,每次追加,都是新建立一個元組。操作系統
首先,咱們看一個例子:
l = [1,2,3]
l.__sizeof__()
64
t = (1,2,3)
t.__sizeof__()
48
複製代碼
對於列表和元組,雖然初始化時放了相同的元素,可是元組的存儲空間,比列表要少16個字節。這是由於,因爲列表是可變對象,須要存儲指針,來指向對應的元素(上述例子中,int型,8字節)。另外,因爲列表可變,須要額外存儲已經分配的長度大小(8字節),這樣才能夠實時追蹤列表空間的使用狀況,當空間不足時,及時分配額外空間。
l = []
l.__sizeof__() // 空列表的存儲空間爲40字節
40
l.append(1)
l.__sizeof__()
72 // 加入了元素1以後,列表爲其分配了能夠存儲4個元素的空間 (72 - 40)/8 = 4
l.append(2)
l.__sizeof__()
72 // 因爲以前分配了空間,因此加入元素2,列表空間不變
l.append(3)
l.__sizeof__()
72 // 同上
l.append(4)
l.__sizeof__()
72 // 同上
l.append(5)
l.__sizeof__()
104 // 加入元素5以後,列表的空間不足,因此又額外分配了能夠存儲4個元素的空間
複製代碼
從上面的例子中,咱們能夠看到列表空間分配的大概過程。爲了減小每次增長、刪除操做時空間分配的開銷,Python每次分配空間時會額外多分配一些。這樣的機制(over-allocating)保證了其操做的高效性;增長、刪除操做的時間複雜度爲O(1)。
但因爲元組是不可變的,長度大小固定,故存儲空間固定。那麼,咱們能夠獲得一個初步結論,對於不可變對象來講,有這樣一個好處,在於內存更有效,佔用更小。
此外,對於不可變對象,好比String,在Python3.6以前,是有一個String Interning的概念的,即以下所示:
c = "hi"
d = "hi"
id(c)
4561935656
id(d)
4561935656
複製代碼
在CPython中,若是建立幾個不可變對象,但都具備相同的值,Python能夠將這幾個對象指向一個。這種優化在3.6以後就取消了,可是不妨礙這種操做更節約內存、節約時間。因此,在內存效率層面,不可變對象,元組更勝一籌。
在項目中,使用元組或者列表,還要給予工程角度去考量,好比返回一個地理位置的座標,必定是元組更合適——不可變對象,值固定。在某些場景下,使用元祖,還能夠避免debug過程當中值變動帶來的一些失誤。這就是仁者見仁智者見智的事情了。
[1].Python: What is the Difference between a List and a Tuple?