Python 中的 list 類型應(yīng)該是我們平時(shí)用的最多一個(gè)數(shù)據(jù)類型苟穆,如果僅從操作方式上看抄课,列表像是數(shù)組和鏈表的綜合體,除了按索引訪問(wèn)外雳旅,還支持插入、追加间聊、刪除等操作攒盈,完全可以當(dāng)作隊(duì)列或棧來(lái)使用,因此哎榴,如果不考慮性能問(wèn)題型豁,列表是一種易用且功能完善的理想數(shù)據(jù)結(jié)構(gòu)。
列表的內(nèi)部結(jié)構(gòu)由兩部分構(gòu)成:(1) 保存元素?cái)?shù)量和內(nèi)存分配計(jì)數(shù)的頭部尚蝌,(2) 存儲(chǔ)元素指針的獨(dú)立數(shù)組迎变。所有的元素使用該數(shù)組來(lái)保存指針引用,并不嵌入實(shí)際的內(nèi)容飘言。
作為使用頻率最高的數(shù)據(jù)結(jié)構(gòu)之一衣形,列表的性能優(yōu)化很重要。固定長(zhǎng)度的頭部結(jié)構(gòu)姿鸿,很容易實(shí)現(xiàn)內(nèi)存復(fù)用谆吴。創(chuàng)建時(shí),優(yōu)先從復(fù)用區(qū)獲瓤猎ぁ句狼;被回收時(shí),除非超出復(fù)用數(shù)量限制(80)热某,否則會(huì)被放回復(fù)用區(qū)腻菇。每次真正需要分配和釋放的是指針數(shù)組。
用數(shù)組而非鏈表存儲(chǔ)元素項(xiàng)引用昔馋,也有實(shí)際考慮筹吐。從讀操作來(lái)看,無(wú)論是遍歷還是基于序號(hào)訪問(wèn)绒极,數(shù)組的性能總是最高的骏令。盡管插入、刪除等變更操作須移動(dòng)內(nèi)存垄提,但也僅僅是指針復(fù)制榔袋,無(wú)論元素大小周拐,不會(huì)有太高消耗。如果列表太大凰兑,或?qū)懖僮鬟h(yuǎn)多于讀操作妥粟,那么應(yīng)該使用針對(duì)性的數(shù)據(jù)結(jié)構(gòu),而非通用設(shè)計(jì)的內(nèi)置列表類型吏够。
另外勾给,指針數(shù)組內(nèi)存分配算法基于元素?cái)?shù)量和剩余空間大小,按相應(yīng)比率進(jìn)行擴(kuò)容或收縮锅知,非逐項(xiàng)進(jìn)行播急。如此,可以避免太過(guò)頻繁的內(nèi)存分配操作售睹。
構(gòu)建列表
顯示指定元素的構(gòu)建語(yǔ)法最為常用桩警,也可基于類型創(chuàng)建實(shí)例,接收可迭代對(duì)象作為初始內(nèi)容昌妹。不同于數(shù)組捶枢,列表僅存儲(chǔ)指針,而對(duì)元素內(nèi)容并不關(guān)心飞崖,故可以是不同類型混合烂叔。
>>> [1, "abc", 3.14] # 顯示指定類型
[1, 'abc', 3.14]
>>> list("abc")
['a', 'b', 'c']
>>> list(range(3))
[0, 1, 2]
另有被稱為推導(dǎo)式(comprehension)的語(yǔ)法,同樣使用方括號(hào)固歪,但以 for 循環(huán)初始化元素蒜鸡,并可選用 if 表達(dá)式作為過(guò)濾條件。PS:一旦將推導(dǎo)式兩邊的方括號(hào)改為小括號(hào)昼牛,推導(dǎo)式就變成了生成器术瓮。
>>> [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [i for i in range(10) if i % 2 == 0]
[0, 2, 4, 6, 8]
>>> (i for i in range(10) if i % 2 == 0) # 生成器
<generator object <genexpr> at 0x102216f10>
>>> l = []
# 推導(dǎo)式的行為等同與以下代碼
>>> l = []
>>> for i in range(10):
... if i % 2 == 0:
... l.append(i)
...
>>> l
[0, 2, 4, 6, 8]
專有名詞 ?Pythonic 的基本意思就是寫出簡(jiǎn)潔優(yōu)雅的 Python 代碼,而推導(dǎo)式就算是其中的一種贰健。
無(wú)論是歷史原因胞四,還是實(shí)現(xiàn)方式,內(nèi)置類型關(guān)注性能要多過(guò)設(shè)計(jì)伶椿。如要實(shí)現(xiàn)自定義列表辜伟,書的作者建議基于 collections.UserList 包裝類型來(lái)實(shí)現(xiàn),因?yàn)槌私y(tǒng)一的 collections.abc 體系外脊另,最重要的是該類型重載并完善了相關(guān)運(yùn)算符方法导狡。
>>> list.__bases__
(<class 'object'>,)
>>> import collections
>>> collections.UserList.__bases__
(<class 'collections.abc.MutableSequence'>,)
# 以加法運(yùn)算符為例,繼承不同的類型偎痛,運(yùn)算會(huì)有不同的結(jié)果
>>> class A(list): pass
>>> type(A("abc") + list("de"))
<class 'list'>
>>> class B(collections.UserList): pass
>>> type(B("abc") + list("de"))
<class '__main__.B'>
最小接口設(shè)計(jì)是個(gè)基本原則旱捧,所以應(yīng)該慎重考慮列表這種功能豐富的類型是否適合作為基類。
操作
列表支持用加法連接多個(gè)列表,乘法運(yùn)算符復(fù)制內(nèi)容枚赡。但同是加法(或乘法)運(yùn)算氓癌,但結(jié)果卻不相同。
>>> a = [1, 2]
>>> b = a
>>> a = a + [3, 4] # 新建列表贫橙,然后與 a 關(guān)聯(lián)
# a贪婉、b 結(jié)果不同,確定 a 指向新對(duì)象
>>> a
[1, 2, 3, 4]
>>> b
[1, 2]
>>> a = [1, 2]
>>> b
[1, 2]
>>> b = a
>>> a += [3, 4] # 直接修改 a 內(nèi)容
# a卢肃、b 結(jié)果相同疲迂,確認(rèn)修改原對(duì)象
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]
>>> a is b
True
編譯器將 += 運(yùn)算符處理成 INPLACE_ADD 操作,也就是修改原數(shù)據(jù)莫湘,而非新建對(duì)象尤蒿。其效果類似于執(zhí)行 list.extend 方法。PS:+= 運(yùn)算符調(diào)用的是 __iadd__ 方法逊脯,沒(méi)有該方法時(shí)优质,再嘗試調(diào)用 __add__ 方法;+ 運(yùn)算符調(diào)用的是 __add__ 方法军洼,該方法會(huì)返回一個(gè)新的對(duì)象,原對(duì)象不修改演怎。
判斷元素是否存在匕争,習(xí)慣使用 in,而非 index 方法爷耀。
>>> 2 in [1, 2]
True
至于刪除操作甘桑,可用索引序號(hào)指定某個(gè)元素,或切片指定刪除某個(gè)范圍的元素歹叮。
>>> a = [1, 2, 3, 4, 5]
>>> del a[4] # 刪除單個(gè)元素
>>> a
[1, 2, 3, 4]
>>> del a[1:3] # 刪除某個(gè)范圍內(nèi)的元素
>>> a
[1, 4]
通過(guò)切片的方式創(chuàng)建新的列表對(duì)象時(shí)跑杭,會(huì)復(fù)制相關(guān)的指針數(shù)據(jù)到新數(shù)組。除了所引用的目標(biāo)相同外咆耿,對(duì)列表自身的修改(插入德谅、刪除等)互不影響。
>>> a = [0, 2, 4, 6]
>>> b = a[:2]
>>> a[0] is b[0] # 復(fù)制引用萨螺,依然指向同一對(duì)象
True
>>> a.insert(1, 1) # 對(duì) a 列表操作窄做,不會(huì)影響 b
>>> a
[0, 1, 2, 4, 6]
>>> b
[0, 2]
復(fù)制的是指針(引用),而非目標(biāo)元素對(duì)象慰技。
對(duì)列表自身的修改互不影響椭盏,但對(duì)目標(biāo)元素對(duì)象的修改是共享的。
利用 bisect 模塊吻商,可向有序列表插入元素掏颊。它使用二分法查找適合的位置,可以用來(lái)實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列或一致性哈希算法艾帐。
>>> d = [0, 2, 4]
>>> import bisect
>>> bisect.insort_left(d, 1) # 插入新元素后乌叶,依然保持有序狀態(tài)
>>> d
[0, 1, 2, 4]
>>> bisect.insort_right(d, 3)
>>> d
[0, 1, 2, 3, 4]
自定義復(fù)合類型盆偿,可通過(guò)重載比較運(yùn)算符( __eq__、__lt__ 等)實(shí)現(xiàn)自定義排序條件枉昏。
元組
盡管兩者并沒(méi)有直接關(guān)系陈肛,但在操作方式上,元組可被當(dāng)作列表的只讀版本使用兄裂。
因元組是不可變類型句旱,它的指針數(shù)組無(wú)須變動(dòng),故一次性完成內(nèi)存分配晰奖。系統(tǒng)會(huì)緩存復(fù)用一定長(zhǎng)度的元組內(nèi)存(含指針數(shù)組)谈撒。創(chuàng)建時(shí),按所需長(zhǎng)度提取復(fù)用匾南,沒(méi)有額外的內(nèi)存分配啃匿。從這一點(diǎn)上來(lái)看,元組的性能要好于列表蛆楞。
Python 3.6 緩存復(fù)用長(zhǎng)度在 20 以內(nèi)的 tuple 內(nèi)存溯乒,每種 2000 上限。
# IPyton
>>> %timeit [1, 2, 3]
53.7 ns ± 1.49 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit (1, 2, 3)
12.9 ns ± 0.027 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
元組支持與列表類似的運(yùn)算符操作豹爹,但不能修改裆悄,每次操作都是返回新的對(duì)象。
>>> a = [1, 2, 3]
>>> id(a)
4529921800
>>> a += [4, 5]
>>> id(a)
4529921800
>>> b = (1, 2, 3)
>>> id(b)
4537321872
>>> b += (4, 5)
>>> id(b)
4537253648
列表因?yàn)橹С植迦氡哿h除等修改操作光稼,所以序號(hào)無(wú)法與元素對(duì)象構(gòu)成固定映射。但元組不同孩等,相同序號(hào)總是返回同一對(duì)象艾君,故可為序號(hào)取個(gè)“別名”(namedtuple)。
>>> import collections
>>> User = collections.namedtuple("User", "name,age") # 創(chuàng)建 User 類型肄方,指定字段
>>> issubclass(User, tuple) # tuple 子類
True
>>> user = User("Min", 22)
>>> user.name, user.age # 使用字段名訪問(wèn)
('Min', 22)
>>> user[0] is user.name # 或依舊使用序號(hào)
True
對(duì)于自定義純數(shù)據(jù)類型冰垄,顯然 namedtuple 要比 class 簡(jiǎn)介。關(guān)鍵在于扒秸,名字要比序號(hào)可讀性更強(qiáng)并且更易維護(hù)播演,其類似于數(shù)字常量維護(hù)。
數(shù)組
數(shù)組與列表伴奥、元組的本質(zhì)區(qū)別在于:元素單一類型和內(nèi)容嵌入写烤。
>>> import array
>>> a = array.array("b", [0x11, 0x22, 0x33, 0x44])
>>> memoryview(a).hex() # 使用內(nèi)存視圖查看,內(nèi)容嵌入而非指針
'11223344'
>>> a = array.array("i")
>>> a.append(100)
>>> a.append(1.23)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: integer argument expected, got float
數(shù)組可直接存儲(chǔ)包括 Unicode 字符在內(nèi)的各種數(shù)字拾徙。
但復(fù)合類型須用 struct洲炊、marshal、pickle 等轉(zhuǎn)換為二進(jìn)制字節(jié)后再進(jìn)行存儲(chǔ)。
數(shù)組于列表類似暂衡,長(zhǎng)度不固定询微,按需擴(kuò)張或收縮內(nèi)存。
>>> a = array.array("i", [1, 2, 3])
>>> a.buffer_info() # 返回緩沖區(qū)的內(nèi)存地址和長(zhǎng)度
(4480106800, 3)
>>> a.extend(range(100000)) # 追加大量?jī)?nèi)容后狂巢,內(nèi)存地址和長(zhǎng)度發(fā)生變化
>>> a.buffer_info()
(4487446528, 100003)
由于可指定更緊湊的數(shù)字類型撑毛,故數(shù)組可節(jié)約更多內(nèi)存。再者唧领,內(nèi)容嵌入也避免了對(duì)象的額外開(kāi)銷藻雌,減少了活躍對(duì)象的數(shù)量和內(nèi)存分配的次數(shù)。
從博客搬運(yùn)到簡(jiǎn)書:原文鏈接