一、 清單和元組基礎
清單和元組都是一個可以放置任意資料類型的有序集合。對于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。