高手如何在Python中使用collections模塊
介 紹
Python 3有許多內(nèi)置的數(shù)據(jù)結(jié)構(gòu),包括元組、字典和列表。數(shù)據(jù)結(jié)構(gòu)為我們提供了一種組織和存儲(chǔ)數(shù)據(jù)的方法。collections模塊能夠幫助我們高效地填充和操作數(shù)據(jù)結(jié)構(gòu)。
在本教程中,我們將通過collections模塊中的三個(gè)類來幫助你處理元組、字典和列表。我們將使用namedtuples來創(chuàng)建帶有命名字段的元組,使用defaultdict來在字典中精確地分組信息,以及使用deque來高效地向一個(gè)類列表對(duì)象的每一邊添加元素。
在本教程中,我們將主要處理一個(gè)魚類清單,當(dāng)在虛構(gòu)的水族箱中添加或刪除魚類時(shí),我們需要修改這些清單。
必備條件
要充分理解本教程,建議你熟悉元組、字典和列表數(shù)據(jù)類型(包括它們的語法),以及如何從它們中檢索數(shù)據(jù)。你可以查看這些教程,以獲得必要的背景信息:
理解Python 3中的元組
理解python3中的字典
Python 3中的理解列表
向元組添加命名字段
Python元組是一個(gè)不可變的,或不可改變的,有序的元素序列。元組經(jīng)常用來表示縱列數(shù)據(jù);例如,一個(gè)CSV文件中的行數(shù)或一個(gè)SQL數(shù)據(jù)庫中的行數(shù)。一個(gè)水族箱可以用一系列元組來記錄它的魚類的庫存。
一個(gè)單獨(dú)的魚類元組:

這個(gè)元組由三個(gè)字符串元素組成。
雖然在某些方面很有用,但是這個(gè)元組并沒有清楚地指明它的每個(gè)字段代表什么。實(shí)際上,元素0是一個(gè)名稱,元素1是一個(gè)物種,元素2是一個(gè)飼養(yǎng)箱。
魚類元組字段說明:

這個(gè)表清楚地表明,該元組的三個(gè)元素都有明確的含義。
來自collections模塊的namedtuple允許你向一個(gè)元組的每個(gè)元素添加顯式名稱,以便在你的Python程序中明確這些元素的含義。
讓我們使用namedtuple來生成一個(gè)類,從而明確地命名魚類元組的每個(gè)元素:

from collections import namedtuple 可以讓你的Python程序訪問namedtuple工廠函數(shù)。namedtuple()函數(shù)調(diào)用會(huì)返回一個(gè)綁定到名稱Fish的類。namedtuple()函數(shù)有兩個(gè)參數(shù):我們的新類“Fish”的期望名稱和命名元素["name"、"species”、“tank"]的一個(gè)列表。
我們可以使用Fish類來表示前面的魚類元組:

如果我們運(yùn)行這段代碼,我們將看到以下輸出:

sammy是使用Fish類進(jìn)行實(shí)例化的。sammy是一個(gè)具有三個(gè)明確命名元素的元組。
sammy的字段可以通過它們的名稱或者一個(gè)傳統(tǒng)的元組索引來訪問:

如果我們運(yùn)行這兩個(gè)print調(diào)用,我們將看到以下輸出:

訪問.species會(huì)返回與使用[1]訪問sammy的第二個(gè)元素相同的值。
使用collections模塊中的namedtuple可以在維護(hù)元組(即它們是不可變的、有序的)的重要屬性的同時(shí)使你的程序更具可讀性。
此外,namedtuple工廠函數(shù)還會(huì)向Fish實(shí)例添加幾個(gè)額外的方法。
使用._asdict()將一個(gè)實(shí)例轉(zhuǎn)換為字典:

如果我們運(yùn)行print,你會(huì)看到如下輸出:

在sammy上調(diào)用.asdict()將返回一個(gè)字典,該字典會(huì)將三個(gè)字段名稱分別映射到它們對(duì)應(yīng)的值。
大于3.8的Python版本輸出這一行的方式可能略有不同。例如,你可能會(huì)看到一個(gè)OrderedDict,而不是這里顯示的普通字典。
備注: 在Python中,帶有前導(dǎo)下劃線的方法通常被認(rèn)為是“私有的”。但是,namedtuple提供的其他方法(如._asdict()、._make()、._replace()等)是公開的。
在字典中收集數(shù)據(jù)
在Python字典中收集數(shù)據(jù)通常是很有用的。collections模塊中的defaultdict可以幫助我們快速、簡潔地將信息集合到字典中。
defaultdict絕不會(huì)引發(fā)一個(gè)KeyError。如果一個(gè)鍵不存在,defaultdict會(huì)插入并返回一個(gè)占位符值來代替:

如果我們運(yùn)行這段代碼,我們將看到如下輸出:

defaultdict會(huì)插入并返回一個(gè)占位符值,而不是引發(fā)一個(gè)KeyError。在本例中,我們將占位符值指定為一個(gè)列表。
相比之下,常規(guī)字典會(huì)在缺失的鍵上引發(fā)一個(gè)KeyError:

如果我們運(yùn)行這段代碼,我們將看到如下輸出:

當(dāng)我們?cè)噲D訪問一個(gè)不存在的鍵時(shí),常規(guī)字典my_regular_dict會(huì)引發(fā)一個(gè)KeyError。
defaultdict的行為與常規(guī)字典不同。defaultdict會(huì)不帶任何參數(shù)調(diào)用占位符值來創(chuàng)建一個(gè)新對(duì)象,而不是在缺失的鍵上引發(fā)一個(gè)KeyError。在本例中,是調(diào)用list()創(chuàng)建一個(gè)空列表。
繼續(xù)我們虛構(gòu)的水族箱示例,假設(shè)我們有一個(gè)表示水族箱清單的魚類元組列表:

水族箱中有三種魚——它們的名字、種類和飼養(yǎng)箱在這三個(gè)元組中都有指出。
我們的目標(biāo)是按飼養(yǎng)箱組織我們的清單—我們想知道每個(gè)飼養(yǎng)箱中存在的魚的列表。換句話說,我們需要一個(gè)能將“tank-a”映射到["Jamie", "Mary"] ,并且將“tank-b”映射到["Jamie"]的字典。
我們可以使用defaultdict來按飼養(yǎng)箱對(duì)魚進(jìn)行分組:

運(yùn)行這段代碼,我們將看到以下輸出:

fish_names_by_tank被聲明為一個(gè)defaultdict,它默認(rèn)會(huì)插入list()而不是引發(fā)一個(gè)KeyError。由于這保證了fish_names_by_tank中的每個(gè)鍵都將指向一個(gè)list,所以我們可以自由地調(diào)用.append()來將名稱添加到每個(gè)飼養(yǎng)箱的列表中。
這里,defaultdict幫助你減少了出現(xiàn)未預(yù)期的KeyErrors的機(jī)會(huì)。減少未預(yù)期的KeyErrors意味著你可以用更少的行更清晰地編寫你的程序。更具體地說,defaultdict習(xí)慣用法讓你避免了手動(dòng)地為每個(gè)飼養(yǎng)箱實(shí)例化一個(gè)空列表。
如果沒有 defaultdict, for循環(huán)體可能看起來更像這樣:

使用常規(guī)字典(而不是defaultdict)意味著for循環(huán)體總是必須檢查fish_names_by_tank中給定的tank是否存在。只有在驗(yàn)證了fish_names_by_tank中已經(jīng)存在tank,或者已經(jīng)使用一個(gè)[]初始化了tank之后,我們才可以添加魚類名稱。
在填充字典時(shí),defaultdict可以幫助我們減少樣板代碼,因?yàn)樗鼜牟灰l(fā)KeyError。
使用deque有效地將元素添加到集合的任意一側(cè)
Python列表是一個(gè)可變的,或可改變的,有序的元素序列。Python可以以常數(shù)時(shí)間向列表添加內(nèi)容(列表的長度對(duì)添加元素所需的時(shí)間沒有影響),但是在列表的開頭插入可能比較慢——隨著列表變大,插入花費(fèi)的時(shí)間也會(huì)增加。
就大O符號(hào)而言,向列表追加元素操作是一個(gè)常數(shù)時(shí)間O(1)操作。相比之下,在列表的開頭插入元素要慢一些,其性能為O(n)。
備注: 軟件工程師經(jīng)常使用稱為“大O”符號(hào)的東西來度量操作的性能。當(dāng)一個(gè)輸入的大小對(duì)執(zhí)行一個(gè)操作所需的時(shí)間沒有影響時(shí),我們就可以說該操作以常數(shù)時(shí)間或 O(1)(“1的大O”)運(yùn)行。正如你在上面所了解的,Python可以以常數(shù)時(shí)間性能(也稱為O(1))向列表中追加元素。
有時(shí)候,輸入的大小會(huì)直接影響運(yùn)行一個(gè)操作所需的時(shí)間。例如,在一個(gè)Python列表的開頭插入元素時(shí),列表中的元素越多,插入操作的運(yùn)行速度越慢。大O符號(hào)使用字母n表示輸入的大小。將項(xiàng)目添加到Python列表的開頭以“線性時(shí)間”或 O(n)(“n的大O”)運(yùn)行。
一般來說,O(1)個(gè)操作比O(n)個(gè)操作更快。
我們可以在一個(gè)Python列表的開頭插入:

如果我們運(yùn)行以上代碼,我們將看到如下輸出:

列表上的.insert(index, object)方法允許我們?cè)趂avorite_fish_list的開頭插入“Alice”。不過,值得注意的是,在一個(gè)列表的開頭插入項(xiàng)具有O(n)性能。隨著favorite_fish_list的長度的增長,在列表的開頭插入一個(gè)魚類的時(shí)間將成比例地增長,所需時(shí)間會(huì)越來越長。
collections模塊中的deque(發(fā)音為“deck”)是一個(gè)類似列表的對(duì)象,它允許我們以常數(shù)時(shí)間(O(1))性能在一個(gè)序列的開頭或末尾插入項(xiàng)。
在一個(gè)deque的開頭插入一個(gè)項(xiàng):

運(yùn)行這段代碼,我們將看到以下輸出:

我們可以使用一個(gè)先前存在的元素集合來實(shí)例化一個(gè)deque,在本例中,該集合是三個(gè)最喜歡的魚類名稱的一個(gè)列表。調(diào)用favorite_fish_deque’s appendleft方法允許我們以O(shè)(1)性能在我們集合的開頭插入一個(gè)項(xiàng)。O(1)性能意味著將一個(gè)項(xiàng)添加到 favorite_fish_deque 的開頭所花費(fèi)的時(shí)間并不會(huì)增加,即使favorite_fish_deque中有數(shù)以千計(jì)甚至數(shù)以百萬計(jì)的元素。
備注: 盡管deque將條目添加到一個(gè)序列的開頭比列表更有效,但并不是deque執(zhí)行所有操作都比列表更高效。例如,訪問deque中的一個(gè)隨機(jī)項(xiàng)具有O(n)性能,但是訪問列表中的一個(gè)隨機(jī)項(xiàng)具有O(1)性能。當(dāng)需要快速地從你的集合的任意一側(cè)插入或刪除元素時(shí),請(qǐng)使用deque。時(shí)間性能的完整比較可以在Python的wiki上找到。
結(jié)論
collections模塊是Python標(biāo)準(zhǔn)庫的一個(gè)功能強(qiáng)大的部分,它允許你簡潔高效地處理數(shù)據(jù)。本教程介紹了collections模塊提供的三個(gè)類,包括namedtuple、 defaultdict和deque。
從這里開始,你可以使用collection模塊的文檔去學(xué)習(xí)更多關(guān)于其他可用類和實(shí)用程序的信息。要廣泛地了解更多關(guān)于Python的知識(shí),你可以閱讀Python 3教程系列中的《如何編寫代碼》。
英文原文:https://davidmuller.github.io/posts/2020/05/08/collections-module-Python3.html
譯者:浣熊君
··· END ···
推薦閱讀:
Python中的高效迭代庫itertools,排列組合隨便求
萬字長文詳解|Python庫collections,讓你擊敗99%的Pythoner
Python初學(xué)者必須吃透這69個(gè)內(nèi)置函數(shù)!
全面理解Python集合,17個(gè)方法全解,看完就夠了
↓掃描關(guān)注本號(hào)↓
