天天看點

02 | 清單和元組到底用哪一個?

一、 清單和元組基礎

  清單和元組都是一個可以放置任意資料類型的有序集合。對于python的清單和元組來說,集合的資料類型不像其他程式設計語言一樣必須要求一緻。

l = [1, 2, 'hello', 'world'] # 清單中同時含有int和string類型的元素
l
[1, 2, 'hello', 'world']

tup = ('jason', 22) # 元組中同時含有int和string類型的元素
tup
('jason', 22)      

  注意了:元組使用小括号,清單使用方括号。

  它們的差別:

  •  清單是動态的,長度大小不确定,可以随意地增加、删減或者改變元素
  •  元組是靜态的,長度大小固定,無法增加、删減或者改變
l = [1, 2, 3, 4]
l[3] = 40 # 和很多語言類似,python中索引同樣從0開始,l[3]表示通路清單的第四個元素
l
[1, 2, 3, 40]

tup = (1, 2, 3, 4)
tup[3] = 40
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment      

  如果想改變元組,需要重新開辟一個新的記憶體,建立新的元組了。例如上面的例子,我們如果想增加元素5給元組,實際上是建立了一個新的元組,然後把原來的兩個元組的值依次填充進去。

  而對于清單來說,由于其是動态的,我們可以直接在清單末尾添加對應的元素。這樣的結果是指改變了原來的元組值,但是不會建立新的清單。

tup = (1, 2, 3, 4)
new_tup = tup + (5, ) # 建立新的元組new_tup,并依次填充原元組的值
new _tup
(1, 2, 3, 4, 5)

l = [1, 2, 3, 4]
l.append(5) # 添加元素5到原清單的末尾
l
[1, 2, 3, 4, 5]      

  

  清單和元組的相同點:

  •   python中的清單和元組都支援負數索引,-1代表最後一個元素,-2代表倒數第二個元素,依次類推。
  •   除了基本的初始化,索引外,清單和元組都支援切片操作。
  •   另外,清單和元組都可以随意嵌套。
  •   當然,兩者也可以通過list()與tuple()進行互換。
# 支援負數索引
l = [1, 2, 3, 4]
l[-1]
4

tup = (1, 2, 3, 4)
tup[-1]
4

# 支援切片操作
l = [1, 2, 3, 4]
l[-1]
4

tup = (1, 2, 3, 4)
tup[-1]      #[]一般是用來取值
4

# 可以随意嵌套
l = [[1, 2, 3], [4, 5]] # 清單的每一個元素也是一個清單

tup = ((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]       

二、 清單和元組存儲方式的差異

  由于清單是動态的,是以需要存儲指針,來指向每個元素的位置。又由于清單是可變的,是以需要額外存儲已經配置設定好的長度大小,這樣才可以實時追蹤清單空間的使用情況,當空間不足時,能夠及時配置設定額外空間。換句話說,就是由于為了降低每次增加、删減操作的空間配置設定的開銷,是以python每次配置設定空間的時候都會多配置設定一些,這樣的機制(over-allocating)保證了操作的高效性:增加/删減的時間複雜度都為O(1)。

  看下面的栗子:

l = []
l.__sizeof__() // 空清單的存儲空間為40位元組,8+4*8
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個元素的空間

l = [1, 2, 3]
l.__sizeof__()
64  # (8+8)*4
tup = (1, 2, 3)
tup.__sizeof__()
48  // (8+8)*3      

  上述例子中,由于int是8位元組,而且清單可變,是以需要額外存儲已經配置設定的長度大小(8位元組),這樣才可以實時追蹤清單空間的使用情況,當空間不足的時候,及時配置設定額外空間。

  但對于元組就不同了。元組大小固定,元素不可變,是以存儲空間固定。

  如果清單和元組存儲的元素的個數是一個億,十億或者更大的數量級,這樣的差異就不能忽略。

三、 清單和元組的性能差異

  由于垃圾回收機制的存在,如果一些變量不被使用,python就會回首他們所占的記憶體,返還給作業系統,以供其他變量或者其他應用使用。但是,python在背景對于靜态資料會做一些資源緩存(resource caching),對于一些靜态變量,比如元組,如果他不被使用并且占用空間不大時,python會暫時緩存這部分記憶體。這樣下次我們再建立同樣大小的元組時,python就不用再向作業系統送出請求,去尋找記憶體,而是直接配置設定之前緩存的記憶體空間,這樣就可以加快程式的運作速度。

  元組的初始化速度要比清單快5倍,但如果是索引操作,兩者的速度差别很小,幾乎忽略不計。當然,如果想增加、删減還是要靠清單,因為對于元組還需要再重建。

四、 清單和元組的使用場景

  1、 如果存儲的資料和數量不變,比如你有一個函數,需要傳回的是一個地點的經緯度,然後直接傳給前段渲染,那麼肯定選用元組合适。

  2、如果存儲的資料或者是數量是可變的,比如社交平台上的一個日志功能,時統計一個使用者一周之内看了哪些使用者的文章,那麼用清單更加合适。

五、總結:

  •     清單和元組都是有序的,可以存儲任意資料類型的集合。
  •     清單是動态的、可變的,可以随意增加和删減。清單的存儲空間略大于元組,性能越遜于元組。
  •     元組是靜态的,長度大小固定,不可以對元素進行增加和删減或者改變。元組對于清單,更加輕量級,性能稍優。
  •     list和tuple的内部實作都是array的形式,list因為可變,是以是一個over-allocate的array,tuple因為不可變,是以長度大小固定。具體可以參照源碼
  •       list: https://github.com/python/cpython/blob/master/Objects/listobject.c.
  •       tuple: https://github.com/python/cpython/blob/master/Objects/tupleobject.c
  •     list()是一個function call,Python的function call會建立stack,并且進行一系列參數檢查的操作,比較expensive;反觀[]是一個内置的C函數,可以直接被調用,是以效率高。
  •     元素不需要改變時:兩三個元素,使用 tuple,元素多一點使用namedtuple。
  •     元素需要改變時:需要高效随機讀取,使用list。
  •     需要關鍵字高效查找,采用 dict。
  •     去重,使用 set。
  •     大型資料節省空間,使用标準庫 array;大型資料高效操作,使用 numpy.array。