亞寵展、全球?qū)櫸锂a(chǎn)業(yè)風(fēng)向標(biāo)——亞洲寵物展覽會(huì)深度解析
1139
2022-05-29
目錄
字典、地圖和哈希表
dict:您的首選詞典
collections.OrderedDict:記住鍵的插入順序
collections.defaultdict:為丟失的鍵返回默認(rèn)值
collections.ChainMap:搜索多個(gè)字典作為單個(gè)映射
types.MappingProxyType:用于制作只讀字典的包裝器
Python 中的字典:總結(jié)
數(shù)組數(shù)據(jù)結(jié)構(gòu)
列表:可變動(dòng)態(tài)數(shù)組
元組:不可變?nèi)萜?/p>
array.array:基本類型數(shù)組
str:Unicode 字符的不可變數(shù)組
bytes:不可變的單字節(jié)數(shù)組
bytearray:?jiǎn)巫止?jié)的可變數(shù)組
Python 中的數(shù)組:總結(jié)
記錄、結(jié)構(gòu)和數(shù)據(jù)傳輸對(duì)象
dict:簡(jiǎn)單數(shù)據(jù)對(duì)象
元組:不可變對(duì)象組
編寫(xiě)自定義類:更多工作,更多控制
dataclasses.dataclass:Python 3.7+ 數(shù)據(jù)類
collections.namedtuple:方便的數(shù)據(jù)對(duì)象
Typing.NamedTuple:改進(jìn)的命名元組
struct.Struct:序列化的 C 結(jié)構(gòu)體
types.SimpleNamespace:花式屬性訪問(wèn)
Python 中的記錄、結(jié)構(gòu)和數(shù)據(jù)對(duì)象:總結(jié)
集和多集
套裝:您的首選套裝
凍結(jié)集:不可變集
collections.Counter:Multisets
Python 中的集合和多重集:總結(jié)
堆棧 (LIFO)
列表:簡(jiǎn)單的內(nèi)置堆棧
collections.deque:快速而健壯的堆棧
queue.LifoQueue:并行計(jì)算的鎖定語(yǔ)義
Python 中的堆棧實(shí)現(xiàn):總結(jié)
隊(duì)列 (FIFO)
列表:非常慢的隊(duì)列
collections.deque:快速而健壯的隊(duì)列
queue.Queue:并行計(jì)算的鎖定語(yǔ)義
multiprocessing.Queue:共享作業(yè)隊(duì)列
Python 中的隊(duì)列:總結(jié)
優(yōu)先隊(duì)列
列表:手動(dòng)排序的隊(duì)列
heapq:基于列表的二叉堆
queue.PriorityQueue:漂亮的優(yōu)先隊(duì)列
Python 中的優(yōu)先隊(duì)列:總結(jié)
結(jié)論:Python 數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu)是您構(gòu)建程序的基本結(jié)構(gòu)。每種數(shù)據(jù)結(jié)構(gòu)都提供了一種特定的數(shù)據(jù)組織方式,因此可以根據(jù)您的用例進(jìn)行有效訪問(wèn)。Python 在其標(biāo)準(zhǔn)庫(kù)中附帶了一組廣泛的數(shù)據(jù)結(jié)構(gòu)。
但是,Python 的命名約定并沒(méi)有提供您在其他語(yǔ)言中會(huì)發(fā)現(xiàn)的相同級(jí)別的清晰度。在Java 中,列表不僅僅是一個(gè)list——它是一個(gè)LinkedList或一個(gè)ArrayList。在 Python 中不是這樣。即使是經(jīng)驗(yàn)豐富的 Python 開(kāi)發(fā)人員有時(shí)也會(huì)懷疑內(nèi)置list類型是作為鏈表還是動(dòng)態(tài)數(shù)組實(shí)現(xiàn)的。
在本教程中,您將學(xué)習(xí):
Python標(biāo)準(zhǔn)庫(kù)中內(nèi)置了哪些常見(jiàn)的抽象數(shù)據(jù)類型
最常見(jiàn)的抽象數(shù)據(jù)類型如何映射到 Python 的命名方案
如何在各種算法中實(shí)際使用抽象數(shù)據(jù)類型
注意:本教程改編自Python Tricks: The Book 中的“Common Data Structures in Python”一章。如果您喜歡下面閱讀的內(nèi)容,請(qǐng)務(wù)必查看本書(shū)的其余部分。
字典、地圖和哈希表
在 Python 中,字典(或簡(jiǎn)稱為dicts)是一個(gè)中心數(shù)據(jù)結(jié)構(gòu)。字典存儲(chǔ)任意數(shù)量的對(duì)象,每個(gè)對(duì)象由唯一的字典鍵標(biāo)識(shí)。
字典也常被稱為映射、哈希映射、查找表或關(guān)聯(lián)數(shù)組。它們?cè)试S對(duì)與給定鍵關(guān)聯(lián)的任何對(duì)象進(jìn)行有效的查找、插入和刪除。
電話簿為字典對(duì)象提供了一個(gè)體面的現(xiàn)實(shí)世界模擬。它們?cè)试S您快速檢索與給定鍵(人名)相關(guān)聯(lián)的信息(電話號(hào)碼)。不必前后翻閱電話簿才能找到某人的號(hào)碼,您可以或多或少直接跳到一個(gè)姓名并查找相關(guān)信息。
當(dāng)涉及到如何組織信息以允許快速查找時(shí),這種類比會(huì)有所不同。但是基本的性能特征是成立的。字典允許您快速找到與給定鍵關(guān)聯(lián)的信息。
字典是計(jì)算機(jī)科學(xué)中最重要和最常用的數(shù)據(jù)結(jié)構(gòu)之一。那么,Python 是如何處理字典的呢?讓我們來(lái)看看核心 Python 和 Python 標(biāo)準(zhǔn)庫(kù)中可用的字典實(shí)現(xiàn)。
dict:您的首選詞典
因?yàn)樽值浞浅V匾琍ython 具有一個(gè)健壯的字典實(shí)現(xiàn),它直接內(nèi)置到核心語(yǔ)言中:dict數(shù)據(jù)類型。
Python 還提供了一些有用的語(yǔ)法糖,用于在程序中使用字典。例如,花括號(hào) ({ }) 字典表達(dá)式語(yǔ)法和字典推導(dǎo)式允許您方便地定義新的字典對(duì)象:
>>>
>>> phonebook = { ... "bob": 7387, ... "alice": 3719, ... "jack": 7052, ... } >>> squares = {x: x * x for x in range(6)} >>> phonebook["alice"] 3719 >>> squares {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
哪些對(duì)象可以用作有效鍵有一些限制。
Python 的字典由可以是任何可散列類型的鍵索引。可散列對(duì)象的散列值在其生命周期內(nèi)永遠(yuǎn)不會(huì)改變(請(qǐng)參閱__hash__),并且可以與其他對(duì)象進(jìn)行比較(請(qǐng)參閱__eq__)。比較相等的可散列對(duì)象必須具有相同的散列值。
像字符串和數(shù)字這樣的不可變類型是可散列的,并且可以很好地用作字典鍵。您也可以將tuple對(duì)象用作字典鍵,只要它們本身只包含可散列類型。
對(duì)于大多數(shù)用例,Python 的內(nèi)置字典實(shí)現(xiàn)將完成您需要的一切。字典經(jīng)過(guò)高度優(yōu)化,是語(yǔ)言許多部分的基礎(chǔ)。例如,堆棧幀中的類屬性和變量都內(nèi)部存儲(chǔ)在字典中。
Python 字典基于經(jīng)過(guò)充分測(cè)試和微調(diào)的哈希表實(shí)現(xiàn),它提供了您期望的性能特征:O?(1) 在一般情況下查找、插入、更新和刪除操作的時(shí)間復(fù)雜度。
沒(méi)有理由不使用dictPython 附帶的標(biāo)準(zhǔn)實(shí)現(xiàn)。但是,存在專門(mén)的第三方詞典實(shí)現(xiàn),例如跳過(guò)列表或基于 B 樹(shù)的詞典。
除了普通dict對(duì)象,Python 的標(biāo)準(zhǔn)庫(kù)還包括許多專門(mén)的字典實(shí)現(xiàn)。這些專用詞典均基于內(nèi)置詞典類(并共享其性能特征),但還包括一些額外的便利功能。
讓我們來(lái)看看它們。
collections.OrderedDict: 記住鍵的插入順序
Python 包含一個(gè)專門(mén)的dict子類,它記住添加到其中的鍵的插入順序:collections.OrderedDict.
注意:?OrderedDict不是核心語(yǔ)言的內(nèi)置部分,必須從collections標(biāo)準(zhǔn)庫(kù)中的模塊中導(dǎo)入。
雖然標(biāo)準(zhǔn)dict實(shí)例在 CPython 3.6 及更高版本中保留了鍵的插入順序,但這只是CPython 實(shí)現(xiàn)的副作用,直到 Python 3.7 才在語(yǔ)言規(guī)范中定義。因此,如果鍵順序?qū)δ乃惴üぷ骱苤匾敲醋詈猛ㄟ^(guò)顯式使用OrderedDict該類來(lái)清楚地傳達(dá)這一點(diǎn):
>>>
>>> import collections >>> d = collections.OrderedDict(one=1, two=2, three=3) >>> d OrderedDict([('one', 1), ('two', 2), ('three', 3)]) >>> d["four"] = 4 >>> d OrderedDict([('one', 1), ('two', 2), ('three', 3), ('four', 4)]) >>> d.keys() odict_keys(['one', 'two', 'three', 'four'])
在Python 3.8 之前,您無(wú)法使用reversed().?只有OrderedDict實(shí)例提供了該功能。即使在Python 3.8,dict而OrderedDict對(duì)象是不完全一樣的。OrderedDict實(shí)例具有一種在普通實(shí)例上不可用的.move_to_end()方法,dict以及.popitem()一種比普通dict實(shí)例更可定制的方法。
collections.defaultdict: 為丟失的鍵返回默認(rèn)值
本defaultdict類是接受它的構(gòu)造函數(shù),其返回值將被使用,如果被請(qǐng)求的密鑰無(wú)法找到一個(gè)可調(diào)用另一個(gè)字典子類。
與在常規(guī)詞典中使用get()或捕獲KeyError異常相比,這可以為您節(jié)省一些輸入并使您的意圖更清晰:
>>>
>>> from collections import defaultdict >>> dd = defaultdict(list) >>> # Accessing a missing key creates it and >>> # initializes it using the default factory, >>> # i.e. list() in this example: >>> dd["dogs"].append("Rufus") >>> dd["dogs"].append("Kathrin") >>> dd["dogs"].append("Mr Sniffles") >>> dd["dogs"] ['Rufus', 'Kathrin', 'Mr Sniffles']
collections.ChainMap: 搜索多個(gè)字典作為單個(gè)映射
該collections.ChainMap數(shù)據(jù)結(jié)構(gòu)將多個(gè)字典分組到一個(gè)映射中。查找一一搜索底層映射,直到找到一個(gè)鍵。插入、更新和刪除只影響添加到鏈中的第一個(gè)映射:
>>>
>>> from collections import ChainMap >>> dict1 = {"one": 1, "two": 2} >>> dict2 = {"three": 3, "four": 4} >>> chain = ChainMap(dict1, dict2) >>> chain ChainMap({'one': 1, 'two': 2}, {'three': 3, 'four': 4}) >>> # ChainMap searches each collection in the chain >>> # from left to right until it finds the key (or fails): >>> chain["three"] 3 >>> chain["one"] 1 >>> chain["missing"] Traceback (most recent call last): File "
types.MappingProxyType: 用于制作只讀字典的包裝器
MappingProxyType是標(biāo)準(zhǔn)字典的包裝器,它提供了對(duì)包裝字典數(shù)據(jù)的只讀視圖。這個(gè)類是在 Python 3.3 中添加的,可用于創(chuàng)建字典的不可變代理版本。
MappingProxyType例如,如果您想從類或模塊返回帶有內(nèi)部狀態(tài)的字典,同時(shí)不鼓勵(lì)對(duì)該對(duì)象的寫(xiě)訪問(wèn),這可能會(huì)有所幫助。UsingMappingProxyType允許您在不必首先創(chuàng)建字典的完整副本的情況下設(shè)置這些限制:
>>>
>>> from types import MappingProxyType >>> writable = {"one": 1, "two": 2} >>> read_only = MappingProxyType(writable) >>> # The proxy is read-only: >>> read_only["one"] 1 >>> read_only["one"] = 23 Traceback (most recent call last): File "
Python 中的字典:總結(jié)
本教程中列出的所有 Python 字典實(shí)現(xiàn)都是內(nèi)置于 Python 標(biāo)準(zhǔn)庫(kù)中的有效實(shí)現(xiàn)。
如果您正在尋找有關(guān)在程序中使用哪種映射類型的一般建議,我會(huì)向您指出內(nèi)置dict數(shù)據(jù)類型。它是一種多功能且經(jīng)過(guò)優(yōu)化的哈希表實(shí)現(xiàn),直接內(nèi)置于核心語(yǔ)言中。
我建議您僅在有超出dict.
所有的實(shí)現(xiàn)都是有效的選項(xiàng),但如果你的代碼大部分時(shí)間依賴于標(biāo)準(zhǔn)的 Python 字典,它會(huì)更清晰、更容易維護(hù)。
數(shù)組數(shù)據(jù)結(jié)構(gòu)
一個(gè)陣列是在大多數(shù)編程語(yǔ)言的基本數(shù)據(jù)結(jié)構(gòu)可用的,并且它具有較寬的范圍跨越不同的算法的用途。
在本節(jié)中,您將了解 Python 中的數(shù)組實(shí)現(xiàn),這些實(shí)現(xiàn)僅使用 Python 標(biāo)準(zhǔn)庫(kù)中包含的核心語(yǔ)言特性或功能。您將看到每種方法的優(yōu)點(diǎn)和缺點(diǎn),因此您可以決定哪種實(shí)現(xiàn)適合您的用例。
但在我們開(kāi)始之前,讓我們先介紹一些基礎(chǔ)知識(shí)。數(shù)組是如何工作的,它們的用途是什么?數(shù)組由固定大小的數(shù)據(jù)記錄組成,允許根據(jù)其索引有效地定位每個(gè)元素:
因?yàn)殛嚵兄朽徑拥拇鎯?chǔ)塊存儲(chǔ)的信息,他們認(rèn)為連續(xù)的數(shù)據(jù)結(jié)構(gòu)(相對(duì)于鏈接數(shù)據(jù)結(jié)構(gòu)如鏈表,例如)。
數(shù)組數(shù)據(jù)結(jié)構(gòu)在現(xiàn)實(shí)世界中的類比是停車(chē)場(chǎng)。您可以將停車(chē)場(chǎng)視為一個(gè)整體并將其視為單個(gè)對(duì)象,但在停車(chē)場(chǎng)內(nèi)有由唯一編號(hào)索引的停車(chē)位。停車(chē)位是車(chē)輛的容器——每個(gè)停車(chē)位可以是空的,也可以是停放汽車(chē)、摩托車(chē)或其他車(chē)輛。
但并非所有停車(chē)場(chǎng)都一樣。一些停車(chē)場(chǎng)可能僅限于一種類型的車(chē)輛。例如,房車(chē)停車(chē)場(chǎng)不允許停放自行車(chē)。受限停車(chē)場(chǎng)對(duì)應(yīng)于類型化數(shù)組數(shù)據(jù)結(jié)構(gòu),該結(jié)構(gòu)只允許存儲(chǔ)具有相同數(shù)據(jù)類型的元素。
在性能方面,根據(jù)元素的索引查找包含在數(shù)組中的元素非常快。在這種情況下,正確的數(shù)組實(shí)現(xiàn)可保證恒定的O?(1) 訪問(wèn)時(shí)間。
Python 在其標(biāo)準(zhǔn)庫(kù)中包含了幾個(gè)類似數(shù)組的數(shù)據(jù)結(jié)構(gòu),每個(gè)數(shù)據(jù)結(jié)構(gòu)的特征都略有不同。讓我們來(lái)看看。
list: 可變動(dòng)態(tài)數(shù)組
列表是核心 Python 語(yǔ)言的一部分。盡管名稱不同,Python 的列表在幕后是作為動(dòng)態(tài)數(shù)組實(shí)現(xiàn)的。
這意味著列表允許添加或刪除元素,并且列表將通過(guò)分配或釋放內(nèi)存自動(dòng)調(diào)整保存這些元素的后備存儲(chǔ)。
Python 列表可以包含任意元素——在 Python 中一切都是對(duì)象,包括函數(shù)。因此,您可以混合和匹配不同類型的數(shù)據(jù)類型,并將它們?nèi)看鎯?chǔ)在一個(gè)列表中。
這可能是一個(gè)強(qiáng)大的功能,但缺點(diǎn)是同時(shí)支持多種數(shù)據(jù)類型意味著數(shù)據(jù)通常不那么緊密。結(jié)果,整個(gè)結(jié)構(gòu)占用了更多空間:
>>>
>>> arr = ["one", "two", "three"] >>> arr[0] 'one' >>> # Lists have a nice repr: >>> arr ['one', 'two', 'three'] >>> # Lists are mutable: >>> arr[1] = "hello" >>> arr ['one', 'hello', 'three'] >>> del arr[1] >>> arr ['one', 'three'] >>> # Lists can hold arbitrary data types: >>> arr.append(23) >>> arr ['one', 'three', 23]
tuple: 不可變?nèi)萜?/p>
就像列表一樣,元組是 Python 核心語(yǔ)言的一部分。然而,與列表不同,Python 的tuple對(duì)象是不可變的。這意味著不能動(dòng)態(tài)添加或刪除元素——元組中的所有元素都必須在創(chuàng)建時(shí)定義。
元組是另一種可以保存任意數(shù)據(jù)類型元素的數(shù)據(jù)結(jié)構(gòu)。擁有這種靈活性是強(qiáng)大的,但同樣,這也意味著數(shù)據(jù)沒(méi)有類型化數(shù)組那么緊密:
>>>
>>> arr = ("one", "two", "three") >>> arr[0] 'one' >>> # Tuples have a nice repr: >>> arr ('one', 'two', 'three') >>> # Tuples are immutable: >>> arr[1] = "hello" Traceback (most recent call last): File "
array.array: 基本類型數(shù)組
Python 的array模塊為基本的 C 風(fēng)格數(shù)據(jù)類型(如字節(jié)、32 位整數(shù)、浮點(diǎn)數(shù)等)提供節(jié)省空間的存儲(chǔ)。
使用array.array該類創(chuàng)建的數(shù)組是可變的,其行為類似于列表,但有一個(gè)重要區(qū)別:它們是限制為單一數(shù)據(jù)類型的類型化數(shù)組。
由于這種限制,array.array具有許多元素的對(duì)象比列表和元組更節(jié)省空間。存儲(chǔ)在其中的元素是緊密包裝的,如果您需要存儲(chǔ)許多相同類型的元素,這會(huì)很有用。
此外,數(shù)組支持許多與常規(guī)列表相同的方法,您可以將它們用作替代品,而無(wú)需對(duì)應(yīng)用程序代碼進(jìn)行其他更改。
>>>
>>> import array >>> arr = array.array("f", (1.0, 1.5, 2.0, 2.5)) >>> arr[1] 1.5 >>> # Arrays have a nice repr: >>> arr array('f', [1.0, 1.5, 2.0, 2.5]) >>> # Arrays are mutable: >>> arr[1] = 23.0 >>> arr array('f', [1.0, 23.0, 2.0, 2.5]) >>> del arr[1] >>> arr array('f', [1.0, 2.0, 2.5]) >>> arr.append(42.0) >>> arr array('f', [1.0, 2.0, 2.5, 42.0]) >>> # Arrays are "typed": >>> arr[1] = "hello" Traceback (most recent call last): File "
str: Unicode 字符的不可變數(shù)組
Python 3.x 使用str對(duì)象將文本數(shù)據(jù)存儲(chǔ)為Unicode 字符的不可變序列。實(shí)際上,這意味著 astr是一個(gè)不可變的字符數(shù)組。奇怪的是,它也是一種遞歸數(shù)據(jù)結(jié)構(gòu)——字符串中的每個(gè)字符本身都是一個(gè)str長(zhǎng)度為 1的對(duì)象。
字符串對(duì)象是空間高效的,因?yàn)樗鼈儽痪o密打包并且專門(mén)用于單一數(shù)據(jù)類型。如果要存儲(chǔ) Unicode 文本,則應(yīng)使用字符串。
因?yàn)樽址?Python 中是不可變的,所以修改字符串需要?jiǎng)?chuàng)建一個(gè)修改后的副本。與可變字符串最接近的等價(jià)物是將單個(gè)字符存儲(chǔ)在列表中:
>>>
>>> arr = "abcd" >>> arr[1] 'b' >>> arr 'abcd' >>> # Strings are immutable: >>> arr[1] = "e" Traceback (most recent call last): File "
bytes: 不可變的單字節(jié)數(shù)組
bytes對(duì)象是單個(gè)字節(jié)的不可變序列,或者是 0 ≤?x?≤ 255范圍內(nèi)的整數(shù)。從概念上講,bytes對(duì)象類似于str對(duì)象,您也可以將它們視為不可變的字節(jié)數(shù)組。
像字符串一樣,bytes有自己的文字語(yǔ)法來(lái)創(chuàng)建對(duì)象并且節(jié)省空間。bytes對(duì)象是不可變的,但與字符串不同的是,有一個(gè)專用的可變字節(jié)數(shù)組數(shù)據(jù)類型,稱為bytearray它們可以解壓縮為:
>>>
>>> arr = bytes((0, 1, 2, 3)) >>> arr[1] 1 >>> # Bytes literals have their own syntax: >>> arr b'\x00\x01\x02\x03' >>> arr = b"\x00\x01\x02\x03" >>> # Only valid `bytes` are allowed: >>> bytes((0, 300)) Traceback (most recent call last): File "
bytearray: 單字節(jié)可變數(shù)組
的bytearray類型是一個(gè)整數(shù)的范圍內(nèi)的可變序列0≤?X?≤255。bytearray對(duì)象是密切相關(guān)的bytes對(duì)象,與主要區(qū)別在于一個(gè)bytearray可以被修改可自由可以覆蓋元件,刪除現(xiàn)有的元素,或添加新的那些。該bytearray對(duì)象將增長(zhǎng),并相應(yīng)地縮小。
Abytearray可以轉(zhuǎn)換回不可變bytes對(duì)象,但這涉及完整復(fù)制存儲(chǔ)的數(shù)據(jù) - 一個(gè)緩慢的操作需要O?(?n?) 時(shí)間:
>>>
>>> arr = bytearray((0, 1, 2, 3)) >>> arr[1] 1 >>> # The bytearray repr: >>> arr bytearray(b'\x00\x01\x02\x03') >>> # Bytearrays are mutable: >>> arr[1] = 23 >>> arr bytearray(b'\x00\x17\x02\x03') >>> arr[1] 23 >>> # Bytearrays can grow and shrink in size: >>> del arr[1] >>> arr bytearray(b'\x00\x02\x03') >>> arr.append(42) >>> arr bytearray(b'\x00\x02\x03*') >>> # Bytearrays can only hold `bytes` >>> # (integers in the range 0 <= x <= 255) >>> arr[1] = "hello" Traceback (most recent call last): File "
Python 中的數(shù)組:總結(jié)
在 Python 中實(shí)現(xiàn)數(shù)組時(shí),您可以選擇許多內(nèi)置數(shù)據(jù)結(jié)構(gòu)。在本節(jié)中,您重點(diǎn)介紹了標(biāo)準(zhǔn)庫(kù)中包含的核心語(yǔ)言功能和數(shù)據(jù)結(jié)構(gòu)。
如果您愿意超越 Python 標(biāo)準(zhǔn)庫(kù),那么像NumPy和pandas這樣的第三方包為科學(xué)計(jì)算和數(shù)據(jù)科學(xué)提供了廣泛的快速數(shù)組實(shí)現(xiàn)。
如果你想限制自己使用 Python 包含的數(shù)組數(shù)據(jù)結(jié)構(gòu),那么這里有一些指導(dǎo)原則:
如果您需要存儲(chǔ)任意對(duì)象,可能具有混合數(shù)據(jù)類型,則使用 alist或 a?tuple,具體取決于您是否需要不可變數(shù)據(jù)結(jié)構(gòu)。
如果您有數(shù)字(整數(shù)或浮點(diǎn))數(shù)據(jù)并且緊密包裝和性能很重要,那么試試array.array.
如果您將文本數(shù)據(jù)表示為 Unicode 字符,則使用 Python 的內(nèi)置str.?如果你需要一個(gè)可變的類似字符串的數(shù)據(jù)結(jié)構(gòu),那么使用 a?listof characters 。
如果要存儲(chǔ)連續(xù)的字節(jié)塊,請(qǐng)使用不可變bytes類型,bytearray如果需要可變數(shù)據(jù)結(jié)構(gòu),則使用a。
在大多數(shù)情況下,我喜歡從一個(gè)簡(jiǎn)單的list.?如果性能或存儲(chǔ)空間成為問(wèn)題,我將稍后專門(mén)研究。大多數(shù)時(shí)候,使用像這樣的通用數(shù)組數(shù)據(jù)結(jié)構(gòu)list會(huì)給你最快的開(kāi)發(fā)速度和最大的編程方便。
我發(fā)現(xiàn)這通常在開(kāi)始時(shí)比從一開(kāi)始就試圖擠出每一滴性能更重要。
記錄、結(jié)構(gòu)和數(shù)據(jù)傳輸對(duì)象
與數(shù)組相比,記錄數(shù)據(jù)結(jié)構(gòu)提供固定數(shù)量的字段。每個(gè)字段都可以有一個(gè)名稱,也可以有不同的類型。
在本節(jié)中,您將看到如何僅使用標(biāo)準(zhǔn)庫(kù)中的內(nèi)置數(shù)據(jù)類型和類在 Python 中實(shí)現(xiàn)記錄、結(jié)構(gòu)和普通舊數(shù)據(jù)對(duì)象。
注意:我在這里松散地使用了記錄的定義。例如,我還將討論像 Python 內(nèi)置的類型,這些類型tuple在嚴(yán)格意義上可能會(huì)也可能不會(huì)被視為記錄,因?yàn)樗鼈儾惶峁┟侄巍?/p>
Python 提供了多種數(shù)據(jù)類型,可用于實(shí)現(xiàn)記錄、結(jié)構(gòu)和數(shù)據(jù)傳輸對(duì)象。在本節(jié)中,您將快速了解每個(gè)實(shí)現(xiàn)及其獨(dú)特的特性。最后,您會(huì)找到一份總結(jié)和一份決策指南,可幫助您做出自己的選擇。
注意:本教程改編自Python Tricks: The Book 中的“Common Data Structures in Python”一章。如果您喜歡正在閱讀的內(nèi)容,請(qǐng)務(wù)必查看本書(shū)的其余部分。
好的,讓我們開(kāi)始吧!
dict: 簡(jiǎn)單數(shù)據(jù)對(duì)象
如所提及的先前,Python字典存儲(chǔ)對(duì)象的任意數(shù)量,每一個(gè)都由唯一的密鑰標(biāo)識(shí)。字典也常被稱為映射或關(guān)聯(lián)數(shù)組,允許高效查找、插入和刪除與給定鍵關(guān)聯(lián)的任何對(duì)象。
在 Python 中使用字典作為記錄數(shù)據(jù)類型或數(shù)據(jù)對(duì)象是可能的。字典在 Python 中很容易創(chuàng)建,因?yàn)樗鼈円宰值湮淖值男问皆谡Z(yǔ)言中內(nèi)置了自己的語(yǔ)法糖。字典語(yǔ)法簡(jiǎn)潔,打字非常方便。
使用字典創(chuàng)建的數(shù)據(jù)對(duì)象是可變的,并且?guī)缀鯖](méi)有針對(duì)拼寫(xiě)錯(cuò)誤的字段名稱的保護(hù),因?yàn)樽侄慰梢噪S時(shí)自由添加和刪除。這兩個(gè)屬性都會(huì)引入令人驚訝的錯(cuò)誤,并且在便利性和錯(cuò)誤恢復(fù)之間總是需要權(quán)衡:
>>>
>>> car1 = { ... "color": "red", ... "mileage": 3812.4, ... "automatic": True, ... } >>> car2 = { ... "color": "blue", ... "mileage": 40231, ... "automatic": False, ... } >>> # Dicts have a nice repr: >>> car2 {'color': 'blue', 'automatic': False, 'mileage': 40231} >>> # Get mileage: >>> car2["mileage"] 40231 >>> # Dicts are mutable: >>> car2["mileage"] = 12 >>> car2["windshield"] = "broken" >>> car2 {'windshield': 'broken', 'color': 'blue', 'automatic': False, 'mileage': 12} >>> # No protection against wrong field names, >>> # or missing/extra fields: >>> car3 = { ... "colr": "green", ... "automatic": False, ... "windshield": "broken", ... }
tuple: 不可變的對(duì)象組
Python 的元組是一種用于對(duì)任意對(duì)象進(jìn)行分組的簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)。元組是不可變的——一旦創(chuàng)建就不能修改。
在性能方面,元組占用的內(nèi)存比CPython 中的列表略少,而且它們的構(gòu)造速度也更快。
正如你在下面的字節(jié)碼反匯編中看到的,構(gòu)造一個(gè)元組常量需要一個(gè)LOAD_CONST操作碼,而構(gòu)造一個(gè)具有相同內(nèi)容的列表對(duì)象需要更多的操作:
>>>
>>> import dis >>> dis.dis(compile("(23, 'a', 'b', 'c')", "", "eval")) 0 LOAD_CONST 4 ((23, "a", "b", "c")) 3 RETURN_VALUE >>> dis.dis(compile("[23, 'a', 'b', 'c']", "", "eval")) 0 LOAD_CONST 0 (23) 3 LOAD_CONST 1 ('a') 6 LOAD_CONST 2 ('b') 9 LOAD_CONST 3 ('c') 12 BUILD_LIST 4 15 RETURN_VALUE
但是,您不應(yīng)過(guò)分強(qiáng)調(diào)這些差異。在實(shí)踐中,性能差異通常可以忽略不計(jì),并且試圖通過(guò)從列表切換到元組來(lái)從程序中榨取額外的性能可能是錯(cuò)誤的方法。
普通元組的一個(gè)潛在缺點(diǎn)是,您存儲(chǔ)在其中的數(shù)據(jù)只能通過(guò)整數(shù)索引訪問(wèn)來(lái)提取。您不能為存儲(chǔ)在元組中的單個(gè)屬性命名。這會(huì)影響代碼的可讀性。
此外,元組始終是一種臨時(shí)結(jié)構(gòu):很難確保兩個(gè)元組具有相同數(shù)量的字段和存儲(chǔ)在其中的相同屬性。
這使得很容易引入不經(jīng)意的錯(cuò)誤,例如混淆字段順序。因此,我建議您盡可能減少元組中存儲(chǔ)的字段數(shù):
>>>
>>> # Fields: color, mileage, automatic >>> car1 = ("red", 3812.4, True) >>> car2 = ("blue", 40231.0, False) >>> # Tuple instances have a nice repr: >>> car1 ('red', 3812.4, True) >>> car2 ('blue', 40231.0, False) >>> # Get mileage: >>> car2[1] 40231.0 >>> # Tuples are immutable: >>> car2[1] = 12 Traceback (most recent call last): File "
編寫(xiě)自定義類:更多工作,更多控制
類允許您為數(shù)據(jù)對(duì)象定義可重用的藍(lán)圖,以確保每個(gè)對(duì)象提供相同的字段集。
使用常規(guī) Python 類作為記錄數(shù)據(jù)類型是可行的,但也需要手動(dòng)工作才能獲得其他實(shí)現(xiàn)的便利功能。例如,向__init__構(gòu)造函數(shù)添加新字段是冗長(zhǎng)的并且需要時(shí)間。
此外,從自定義類實(shí)例化的對(duì)象的默認(rèn)字符串表示形式也不是很有幫助。要解決這個(gè)問(wèn)題,您可能必須添加自己的__repr__方法,這通常同樣非常冗長(zhǎng),每次添加新字段時(shí)都必須更新。
存儲(chǔ)在類中的字段是可變的,可以自由添加新字段,您可能喜歡也可能不喜歡。可以提供更多的訪問(wèn)控制并使用@property裝飾器創(chuàng)建只讀字段,但同樣,這需要編寫(xiě)更多的膠水代碼。
每當(dāng)您想使用方法向記錄對(duì)象添加業(yè)務(wù)邏輯和行為時(shí),編寫(xiě)自定義類是一個(gè)很好的選擇。但是,這意味著這些對(duì)象在技術(shù)上不再是純數(shù)據(jù)對(duì)象:
>>>
>>> class Car: ... def __init__(self, color, mileage, automatic): ... self.color = color ... self.mileage = mileage ... self.automatic = automatic ... >>> car1 = Car("red", 3812.4, True) >>> car2 = Car("blue", 40231.0, False) >>> # Get the mileage: >>> car2.mileage 40231.0 >>> # Classes are mutable: >>> car2.mileage = 12 >>> car2.windshield = "broken" >>> # String representation is not very useful >>> # (must add a manually written __repr__ method): >>> car1
dataclasses.dataclass:Python 3.7+ 數(shù)據(jù)類
數(shù)據(jù)類在 Python 3.7 及更高版本中可用。它們?yōu)閺念^開(kāi)始定義您自己的數(shù)據(jù)存儲(chǔ)類提供了一種極好的替代方法。
通過(guò)編寫(xiě)數(shù)據(jù)類而不是普通的 Python 類,您的對(duì)象實(shí)例可以獲得一些開(kāi)箱即用的有用功能,這將為您節(jié)省一些鍵入和手動(dòng)實(shí)現(xiàn)的工作:
定義實(shí)例變量的語(yǔ)法更短,因?yàn)槟恍枰獙?shí)現(xiàn)該.__init__()方法。
數(shù)據(jù)類的實(shí)例通過(guò)自動(dòng)生成的.__repr__()方法自動(dòng)獲得漂亮的字符串表示。
實(shí)例變量接受類型注釋,使您的數(shù)據(jù)類在一定程度上具有自文檔性。請(qǐng)記住,類型注釋只是提示,沒(méi)有單獨(dú)的類型檢查工具就不會(huì)強(qiáng)制執(zhí)行。
數(shù)據(jù)類通常使用@dataclass?裝飾器創(chuàng)建,如下面的代碼示例所示:
>>>
>>> from dataclasses import dataclass >>> @dataclass ... class Car: ... color: str ... mileage: float ... automatic: bool ... >>> car1 = Car("red", 3812.4, True) >>> # Instances have a nice repr: >>> car1 Car(color='red', mileage=3812.4, automatic=True) >>> # Accessing fields: >>> car1.mileage 3812.4 >>> # Fields are mutable: >>> car1.mileage = 12 >>> car1.windshield = "broken" >>> # Type annotations are not enforced without >>> # a separate type checking tool like mypy: >>> Car("red", "NOT_A_FLOAT", 99) Car(color='red', mileage='NOT_A_FLOAT', automatic=99)
要了解有關(guān) Python 數(shù)據(jù)類的更多信息,請(qǐng)查看Python 3.7 中的數(shù)據(jù)類終極指南。
collections.namedtuple: 方便的數(shù)據(jù)對(duì)象
namedtuplePython 2.6+ 中可用的類提供了內(nèi)置tuple數(shù)據(jù)類型的擴(kuò)展。與定義自定義類類似, usingnamedtuple允許您為記錄定義可重用的藍(lán)圖,以確保使用正確的字段名稱。
namedtuple對(duì)象是不可變的,就像普通元組一樣。這意味著您不能在namedtuple創(chuàng)建實(shí)例后添加新字段或修改現(xiàn)有字段。
除此之外,namedtuple對(duì)象是,嗯。.?.?命名元組。存儲(chǔ)在其中的每個(gè)對(duì)象都可以通過(guò)唯一標(biāo)識(shí)符進(jìn)行訪問(wèn)。這使您不必記住整數(shù)索引或求助于解決方法,例如將整數(shù)常量定義為索引的助記符。
namedtuple對(duì)象在內(nèi)部實(shí)現(xiàn)為常規(guī) Python 類。在內(nèi)存使用方面,它們也比常規(guī)類更好,并且與常規(guī)元組一樣具有內(nèi)存效率:
>>>
>>> from collections import namedtuple >>> from sys import getsizeof >>> p1 = namedtuple("Point", "x y z")(1, 2, 3) >>> p2 = (1, 2, 3) >>> getsizeof(p1) 64 >>> getsizeof(p2) 64
namedtuple?對(duì)象可以是一種清理代碼的簡(jiǎn)單方法,并通過(guò)為數(shù)據(jù)實(shí)施更好的結(jié)構(gòu)使其更具可讀性。
我發(fā)現(xiàn)從具有固定格式的字典等特殊數(shù)據(jù)類型到namedtuple對(duì)象有助于我更清楚地表達(dá)我的代碼的意圖。通常,當(dāng)我應(yīng)用這種重構(gòu)時(shí),我會(huì)神奇地為我面臨的問(wèn)題想出一個(gè)更好的解決方案。
namedtuple在常規(guī)(非結(jié)構(gòu)化)元組和字典上使用對(duì)象還可以使傳遞的數(shù)據(jù)自我記錄,至少在一定程度上使您的同事的生活更輕松:
>>>
>>> from collections import namedtuple >>> Car = namedtuple("Car" , "color mileage automatic") >>> car1 = Car("red", 3812.4, True) >>> # Instances have a nice repr: >>> car1 Car(color="red", mileage=3812.4, automatic=True) >>> # Accessing fields: >>> car1.mileage 3812.4 >>> # Fields are immtuable: >>> car1.mileage = 12 Traceback (most recent call last): File "
typing.NamedTuple: 改進(jìn)的命名元組
在 Python 3.6 中添加,typing.NamedTuple是模塊中namedtuple類的弟弟collections。它與 非常相似namedtuple,主要區(qū)別在于用于定義新記錄類型的更新語(yǔ)法和增加了對(duì)類型提示的支持。
請(qǐng)注意,如果沒(méi)有像mypy這樣的單獨(dú)類型檢查工具,則不會(huì)強(qiáng)制執(zhí)行類型注釋。但即使沒(méi)有工具支持,它們也可以為其他程序員提供有用的提示(如果類型提示過(guò)時(shí),則會(huì)非常混亂):
>>>
>>> from typing import NamedTuple >>> class Car(NamedTuple): ... color: str ... mileage: float ... automatic: bool >>> car1 = Car("red", 3812.4, True) >>> # Instances have a nice repr: >>> car1 Car(color='red', mileage=3812.4, automatic=True) >>> # Accessing fields: >>> car1.mileage 3812.4 >>> # Fields are immutable: >>> car1.mileage = 12 Traceback (most recent call last): File "
struct.Struct: 序列化的 C 結(jié)構(gòu)
所述struct.Struct的Python值和C的結(jié)構(gòu)之間轉(zhuǎn)換類序列化到的Pythonbytes對(duì)象。例如,它可用于處理存儲(chǔ)在文件中或來(lái)自網(wǎng)絡(luò)連接的二進(jìn)制數(shù)據(jù)。
結(jié)構(gòu)是使用基于迷你語(yǔ)言定義的格式字符串,允許你定義各種C數(shù)據(jù)類型,如的安排char,int以及l(fā)ong以及其unsigned變種。
序列化結(jié)構(gòu)很少用于表示純粹在 Python 代碼中處理的數(shù)據(jù)對(duì)象。它們主要用作數(shù)據(jù)交換格式,而不是作為一種僅由 Python 代碼使用的將數(shù)據(jù)保存在內(nèi)存中的方式。
在某些情況下,將原始數(shù)據(jù)打包到結(jié)構(gòu)中可能比將其保留在其他數(shù)據(jù)類型中使用更少的內(nèi)存。但是,在大多數(shù)情況下,這將是一種非常高級(jí)(并且可能是不必要的)優(yōu)化:
>>>
>>> from struct import Struct >>> MyStruct = Struct("i?f") >>> data = MyStruct.pack(23, False, 42.0) >>> # All you get is a blob of data: >>> data b'\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00(B' >>> # Data blobs can be unpacked again: >>> MyStruct.unpack(data) (23, False, 42.0)
types.SimpleNamespace: 花式屬性訪問(wèn)
這是在 Python 中實(shí)現(xiàn)數(shù)據(jù)對(duì)象的另一種略顯晦澀的選擇:types.SimpleNamespace.?這個(gè)類是在 Python 3.3 中添加的,并提供對(duì)其命名空間的屬性訪問(wèn)。
這意味著SimpleNamespace實(shí)例將其所有鍵公開(kāi)為類屬性。您可以使用obj.key點(diǎn)屬性訪問(wèn)代替obj['key']常規(guī)字典使用的方括號(hào)索引語(yǔ)法。__repr__默認(rèn)情況下,所有實(shí)例還包括一個(gè)有意義的。
顧名思義,SimpleNamespace簡(jiǎn)單!它基本上是一個(gè)允許屬性訪問(wèn)和打印良好的字典。可以自由添加、修改和刪除屬性:
>>>
>>> from types import SimpleNamespace >>> car1 = SimpleNamespace(color="red", mileage=3812.4, automatic=True) >>> # The default repr: >>> car1 namespace(automatic=True, color='red', mileage=3812.4) >>> # Instances support attribute access and are mutable: >>> car1.mileage = 12 >>> car1.windshield = "broken" >>> del car1.automatic >>> car1 namespace(color='red', mileage=12, windshield='broken')
Python 中的記錄、結(jié)構(gòu)和數(shù)據(jù)對(duì)象:總結(jié)
如您所見(jiàn),實(shí)現(xiàn)記錄或數(shù)據(jù)對(duì)象有很多不同的選項(xiàng)。你應(yīng)該為 Python 中的數(shù)據(jù)對(duì)象使用哪種類型?通常,您的決定將取決于您的用例:
如果您只有幾個(gè)字段,那么如果字段順序易于記憶或字段名稱是多余的,則使用純?cè)M對(duì)象可能沒(méi)問(wèn)題。例如,考慮(x, y, z)三維空間中的一個(gè)點(diǎn)。
如果您需要不可變字段,那么純?cè)Mcollections.namedtuple、 和typing.NamedTuple都是不錯(cuò)的選擇。
如果您需要鎖定字段名稱以避免拼寫(xiě)錯(cuò)誤,那么collections.namedtuple和typing.NamedTuple就是您的朋友。
如果您想讓事情保持簡(jiǎn)單,那么簡(jiǎn)單的字典對(duì)象可能是一個(gè)不錯(cuò)的選擇,因?yàn)樗恼Z(yǔ)法非常類似于JSON。
如果您需要完全控制您的數(shù)據(jù)結(jié)構(gòu),那么是時(shí)候使用@propertysetter 和 getter編寫(xiě)自定義類了。
如果您需要向?qū)ο筇砑有袨椋ǚ椒ǎ敲茨鷳?yīng)該從頭開(kāi)始編寫(xiě)自定義類,或者使用dataclass裝飾器,或者通過(guò)擴(kuò)展collections.namedtuple或typing.NamedTuple。
如果您需要將數(shù)據(jù)緊密打包以將其序列化到磁盤(pán)或通過(guò)網(wǎng)絡(luò)發(fā)送,那么是時(shí)候繼續(xù)閱讀了,struct.Struct因?yàn)檫@是一個(gè)很好的用例!
如果您正在尋找安全的默認(rèn)選擇,那么我在 Python 中實(shí)現(xiàn)普通記錄、結(jié)構(gòu)或數(shù)據(jù)對(duì)象的一般建議是collections.namedtuple在 Python 2.x 及其更小的兄弟typing.NamedTuplePython 3 中使用。
集和多集
在本節(jié)中,您將看到如何使用標(biāo)準(zhǔn)庫(kù)中的內(nèi)置數(shù)據(jù)類型和類在 Python 中實(shí)現(xiàn)可變和不可變的集合和多集(袋)數(shù)據(jù)結(jié)構(gòu)。
一組是不允許重復(fù)元素的對(duì)象的無(wú)序集合。通常,集合用于快速測(cè)試集合中成員的值,從集合中插入或刪除新值,以及計(jì)算兩個(gè)集合的并集或交集。
在適當(dāng)?shù)募蠈?shí)現(xiàn)中,成員資格測(cè)試預(yù)計(jì)在快速O?(1) 時(shí)間內(nèi)運(yùn)行。并、交、差和子集操作平均需要O?(?n?) 時(shí)間。Python 標(biāo)準(zhǔn)庫(kù)中包含的集合實(shí)現(xiàn)遵循這些性能特征。
就像字典一樣,集合在 Python 中得到了特殊處理,并且有一些語(yǔ)法糖,使它們易于創(chuàng)建。例如,花括號(hào)集合表達(dá)式語(yǔ)法和集合推導(dǎo)式允許您方便地定義新的集合實(shí)例:
vowels = {"a", "e", "i", "o", "u"} squares = {x * x for x in range(10)}
但要小心:要?jiǎng)?chuàng)建一個(gè)空集,您需要調(diào)用set()構(gòu)造函數(shù)。使用空花括號(hào) (?{}) 是不明確的,而是會(huì)創(chuàng)建一個(gè)空字典。
Python 及其標(biāo)準(zhǔn)庫(kù)提供了幾個(gè)集合實(shí)現(xiàn)。讓我們來(lái)看看它們。
set:您的首選套裝
該set類型是在Python內(nèi)置集的實(shí)現(xiàn)。它是可變的,并允許動(dòng)態(tài)插入和刪除元素。
Python 的集合由dict數(shù)據(jù)類型支持并共享相同的性能特征。任何可散列對(duì)象都可以存儲(chǔ)在一個(gè)集合中:
>>>
>>> vowels = {"a", "e", "i", "o", "u"} >>> "e" in vowels True >>> letters = set("alice") >>> letters.intersection(vowels) {'a', 'e', 'i'} >>> vowels.add("x") >>> vowels {'i', 'a', 'u', 'o', 'x', 'e'} >>> len(vowels) 6
frozenset: 不可變集
本frozenset類實(shí)現(xiàn)的不可變版本set不能后更改的被構(gòu)建。
frozenset對(duì)象是靜態(tài)的,只允許對(duì)其元素進(jìn)行查詢操作,而不是插入或刪除。因?yàn)閒rozenset對(duì)象是靜態(tài)且可散列的,所以它們可以用作字典鍵或另一個(gè)集合的元素,這是常規(guī)(可變)set對(duì)象無(wú)法實(shí)現(xiàn)的:
>>>
>>> vowels = frozenset({"a", "e", "i", "o", "u"}) >>> vowels.add("p") Traceback (most recent call last): File "
collections.Counter: 多組
collections.CounterPython 標(biāo)準(zhǔn)庫(kù)中的類實(shí)現(xiàn)了多集或包類型,允許集合中的元素出現(xiàn)多次。
如果你需要保持跟蹤這不僅是有用,如果一個(gè)元素是一組的一部分,但也多少次它包含在集:
>>>
>>> from collections import Counter >>> inventory = Counter() >>> loot = {"sword": 1, "bread": 3} >>> inventory.update(loot) >>> inventory Counter({'bread': 3, 'sword': 1}) >>> more_loot = {"sword": 1, "apple": 1} >>> inventory.update(more_loot) >>> inventory Counter({'bread': 3, 'sword': 2, 'apple': 1})
Counter該類的一個(gè)警告是,在計(jì)算Counter對(duì)象中元素的數(shù)量時(shí)要小心。調(diào)用len()返回多集中唯一元素的數(shù)量,而可以使用以下方法檢索元素總數(shù)sum():
>>>
>>> len(inventory) 3 # Unique elements >>> sum(inventory.values()) 6 # Total no. of elements
Python 中的集合和多重集:總結(jié)
集合是 Python 及其標(biāo)準(zhǔn)庫(kù)中包含的另一種有用且常用的數(shù)據(jù)結(jié)構(gòu)。以下是決定使用哪一個(gè)的一些準(zhǔn)則:
如果您需要可變集,請(qǐng)使用內(nèi)置set類型。
如果您需要可用作字典或設(shè)置鍵的可散列對(duì)象,請(qǐng)使用frozenset.
如果您需要多集或包數(shù)據(jù)結(jié)構(gòu),請(qǐng)使用collections.Counter.
堆棧 (LIFO)
甲堆是對(duì)象的集合,支持快速后進(jìn)/先出(LIFO)語(yǔ)義插入和刪除。與列表或數(shù)組不同,堆棧通常不允許隨機(jī)訪問(wèn)它們包含的對(duì)象。插入和刪除操作通常也稱為push和pop。
堆棧數(shù)據(jù)結(jié)構(gòu)的一個(gè)有用的現(xiàn)實(shí)世界類比是一堆盤(pán)子。新的盤(pán)子被添加到堆棧的頂部,由于盤(pán)子又貴又重,只能移動(dòng)最上面的盤(pán)子。換句話說(shuō),堆棧中的最后一個(gè)板必須是第一個(gè)移除的 (LIFO)。要到達(dá)堆棧中較低的板,必須將最頂部的板一一移除。
在性能方面,正確的堆棧實(shí)現(xiàn)預(yù)計(jì)需要O?(1) 時(shí)間進(jìn)行插入和刪除操作。
堆棧在算法中有廣泛的用途。例如,它們用于語(yǔ)言解析以及依賴于調(diào)用堆棧的運(yùn)行時(shí)內(nèi)存管理。使用堆棧的一種簡(jiǎn)短而美觀的算法是對(duì)樹(shù)或圖形數(shù)據(jù)結(jié)構(gòu)的深度優(yōu)先搜索(DFS)。
Python 附帶了幾個(gè)堆棧實(shí)現(xiàn),每個(gè)實(shí)現(xiàn)的特性略有不同。讓我們來(lái)看看它們并比較它們的特性。
list:簡(jiǎn)單的內(nèi)置堆棧
Python 的內(nèi)置list類型構(gòu)成了一個(gè)不錯(cuò)的堆棧數(shù)據(jù)結(jié)構(gòu),因?yàn)樗С址謹(jǐn)?O?(1) 時(shí)間的push 和 pop 操作。
Python 的列表在內(nèi)部實(shí)現(xiàn)為動(dòng)態(tài)數(shù)組,這意味著當(dāng)添加或刪除元素時(shí),它們偶爾需要調(diào)整存儲(chǔ)在其中的元素的存儲(chǔ)空間大小。該列表過(guò)度分配了其后備存儲(chǔ),因此并非每次推送或彈出都需要調(diào)整大小。因此,這些操作的分?jǐn)倳r(shí)間復(fù)雜度為O?(1)。
不利的一面是,這使得它們的性能不如基于鏈表的實(shí)現(xiàn)提供的穩(wěn)定的O?(1) 插入和刪除(如下面的collections.deque)。另一方面,列表確實(shí)提供了對(duì)堆棧上元素的快速O?(1) 時(shí)間隨機(jī)訪問(wèn),這可能是一個(gè)額外的好處。
使用列表作為堆棧時(shí),您應(yīng)該注意一個(gè)重要的性能警告:為了獲得插入和刪除的分?jǐn)侽?(1) 性能,必須使用該方法將新項(xiàng)目添加到列表的末尾,append()并再次從列表中刪除結(jié)束使用pop().?為了獲得最佳性能,基于 Python 列表的堆棧應(yīng)該向更高的索引增長(zhǎng)并向更低的索引收縮。
從前面添加和刪除要慢得多并且需要O?(?n?) 時(shí)間,因?yàn)楸仨氁苿?dòng)現(xiàn)有元素為新元素騰出空間。這是您應(yīng)該盡可能避免的性能反模式:
>>>
>>> s = [] >>> s.append("eat") >>> s.append("sleep") >>> s.append("code") >>> s ['eat', 'sleep', 'code'] >>> s.pop() 'code' >>> s.pop() 'sleep' >>> s.pop() 'eat' >>> s.pop() Traceback (most recent call last): File "
collections.deque:快速而健壯的堆棧
所述deque類實(shí)現(xiàn)一個(gè)雙端隊(duì)列支持添加和去除從任一端元件?(1)時(shí)間(非攤銷(xiāo))。由于雙端隊(duì)列同樣支持從任一端添加和刪除元素,因此它們既可以用作隊(duì)列,也可以用作堆棧。
Python 的deque對(duì)象被實(shí)現(xiàn)為雙向鏈表,這使它們?cè)诓迦牒蛣h除元素方面具有出色且一致的性能,但在隨機(jī)訪問(wèn)堆棧中間的元素方面的O?(?n?) 性能較差。
總的來(lái)說(shuō),如果您正在 Python 標(biāo)準(zhǔn)庫(kù)中尋找具有鏈表實(shí)現(xiàn)性能特征的堆棧數(shù)據(jù)結(jié)構(gòu),這collections.deque是一個(gè)不錯(cuò)的選擇:
>>>
>>> from collections import deque >>> s = deque() >>> s.append("eat") >>> s.append("sleep") >>> s.append("code") >>> s deque(['eat', 'sleep', 'code']) >>> s.pop() 'code' >>> s.pop() 'sleep' >>> s.pop() 'eat' >>> s.pop() Traceback (most recent call last): File "
queue.LifoQueue: 并行計(jì)算的鎖定語(yǔ)義
LifoQueuePython 標(biāo)準(zhǔn)庫(kù)中的堆棧實(shí)現(xiàn)是同步的,并提供鎖定語(yǔ)義以支持多個(gè)并發(fā)生產(chǎn)者和消費(fèi)者。
此外LifoQueue,該queue模塊還包含其他幾個(gè)實(shí)現(xiàn)多生產(chǎn)者、多消費(fèi)者隊(duì)列的類,這些隊(duì)列對(duì)并行計(jì)算很有用。
根據(jù)您的用例,鎖定語(yǔ)義可能會(huì)有所幫助,或者它們可能只會(huì)產(chǎn)生不必要的開(kāi)銷(xiāo)。在這種情況下,最好使用 alist或 adeque作為通用堆棧:
>>>
>>> from queue import LifoQueue >>> s = LifoQueue() >>> s.put("eat") >>> s.put("sleep") >>> s.put("code") >>> s
Python 中的堆棧實(shí)現(xiàn):總結(jié)
如您所見(jiàn),Python 附帶了多種堆棧數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)。它們都具有略有不同的特性以及性能和使用權(quán)衡。
如果您不是在尋找并行處理支持(或者如果您不想手動(dòng)處理鎖定和解鎖),那么您的選擇歸結(jié)為內(nèi)置list類型或collections.deque.?不同之處在于幕后使用的數(shù)據(jù)結(jié)構(gòu)和整體易用性。
list?由動(dòng)態(tài)數(shù)組支持,這使得它非常適合快速隨機(jī)訪問(wèn),但在添加或刪除元素時(shí)需要偶爾調(diào)整大小。
該列表過(guò)度分配了其后備存儲(chǔ),因此并非每個(gè)推送或彈出都需要調(diào)整大小,并且這些操作的分?jǐn)倳r(shí)間復(fù)雜度為O?(1)。但是您確實(shí)需要小心,只能使用append()and插入和刪除項(xiàng)目pop()。否則,性能會(huì)降低到O?(?n?)。
collections.deque由雙向鏈表支持,它優(yōu)化了兩端的追加和刪除,并為這些操作提供一致的O?(1) 性能。不僅其性能更穩(wěn)定,deque該類也更易于使用,因?yàn)槟槐負(fù)?dān)心從錯(cuò)誤的一端添加或刪除項(xiàng)目。
總之,collections.deque是在 Python 中實(shí)現(xiàn)堆棧(LIFO 隊(duì)列)的絕佳選擇。
隊(duì)列 (FIFO)
在本節(jié)中,您將了解如何僅使用 Python 標(biāo)準(zhǔn)庫(kù)中的內(nèi)置數(shù)據(jù)類型和類來(lái)實(shí)現(xiàn)先進(jìn)/先出(FIFO) 隊(duì)列數(shù)據(jù)結(jié)構(gòu)。
甲隊(duì)列是對(duì)象的集合支持用于插入和刪除快速FIFO語(yǔ)義。插入和刪除操作有時(shí)稱為入隊(duì)和出隊(duì)。與列表或數(shù)組不同,隊(duì)列通常不允許隨機(jī)訪問(wèn)它們包含的對(duì)象。
這是一個(gè)真實(shí)世界的 FIFO 隊(duì)列類比:
想象一下,在 PyCon 注冊(cè)的第一天,一群 Pythonistas 正在等待領(lǐng)取他們的會(huì)議徽章。當(dāng)新人進(jìn)入會(huì)場(chǎng)并排隊(duì)領(lǐng)取胸卡時(shí),他們會(huì)在隊(duì)列的后面加入隊(duì)伍(排隊(duì))。開(kāi)發(fā)人員收到他們的徽章和會(huì)議贓物袋,然后在隊(duì)列的前面退出隊(duì)列(出隊(duì))。
記住隊(duì)列數(shù)據(jù)結(jié)構(gòu)特征的另一種方法是將其視為管道。您在一端添加乒乓球,然后它們移動(dòng)到另一端,在那里您將它們移除。當(dāng)球在隊(duì)列中(一根堅(jiān)固的金屬管)時(shí),你無(wú)法拿到它們。與隊(duì)列中的球交互的唯一方法是在管道的后面添加新的(入隊(duì))或在前面刪除它們(出隊(duì))。
隊(duì)列類似于棧。它們之間的區(qū)別在于如何刪除項(xiàng)目。隨著隊(duì)列,你刪除該項(xiàng)目至少最近添加(FIFO),但有一個(gè)堆棧,你刪除的項(xiàng)最最近添加(LIFO)。
在性能方面,正確的隊(duì)列實(shí)現(xiàn)預(yù)計(jì)需要O?(1) 時(shí)間來(lái)進(jìn)行插入和刪除操作。這是對(duì)隊(duì)列執(zhí)行的兩個(gè)主要操作,在正確的實(shí)現(xiàn)中,它們應(yīng)該很快。
隊(duì)列在算法中有廣泛的應(yīng)用,通常有助于解決調(diào)度和并行編程問(wèn)題。使用隊(duì)列的一種簡(jiǎn)短而美觀的算法是對(duì)樹(shù)或圖數(shù)據(jù)結(jié)構(gòu)的廣度優(yōu)先搜索(BFS)。
調(diào)度算法通常在內(nèi)部使用優(yōu)先級(jí)隊(duì)列。這些是專門(mén)的隊(duì)列。代替通過(guò)插入時(shí)間檢索的下一個(gè)元素的,一個(gè)優(yōu)先級(jí)隊(duì)列中檢索最高優(yōu)先級(jí)的元素。單個(gè)元素的優(yōu)先級(jí)由隊(duì)列根據(jù)應(yīng)用于它們的鍵的順序決定。
但是,常規(guī)隊(duì)列不會(huì)對(duì)其攜帶的項(xiàng)目重新排序。就像在管道示例中一樣,您取出放入的內(nèi)容,并且完全按照該順序。
Python 附帶了幾個(gè)隊(duì)列實(shí)現(xiàn),每個(gè)實(shí)現(xiàn)的特性略有不同。讓我們回顧一下它們。
list:非常慢的隊(duì)列
可以將常規(guī)list用作 queue,但從性能角度來(lái)看這并不理想。為此,列表非常慢,因?yàn)樵陂_(kāi)頭插入或刪除一個(gè)元素需要將所有其他元素移動(dòng)一個(gè),需要O?(?n?) 時(shí)間。
因此,除非您只處理少量元素,否則我不建議將 alist用作 Python 中的臨時(shí)隊(duì)列:
>>>
>>> q = [] >>> q.append("eat") >>> q.append("sleep") >>> q.append("code") >>> q ['eat', 'sleep', 'code'] >>> # Careful: This is slow! >>> q.pop(0) 'eat'
collections.deque:快速而健壯的隊(duì)列
所述deque類實(shí)現(xiàn)一個(gè)雙端隊(duì)列支持添加和去除從任一端元件?(1)時(shí)間(非攤銷(xiāo))。由于雙端隊(duì)列同樣支持從任一端添加和刪除元素,因此它們既可以用作隊(duì)列,也可以用作堆棧。
Python 的deque對(duì)象被實(shí)現(xiàn)為雙向鏈表。這使他們用于插入和刪除元素優(yōu)良和穩(wěn)定的性能,但差?(?用于隨機(jī)訪問(wèn)在堆棧的中間元件)的性能。
因此,collections.deque如果您正在 Python 的標(biāo)準(zhǔn)庫(kù)中尋找隊(duì)列數(shù)據(jù)結(jié)構(gòu),這是一個(gè)很好的默認(rèn)選擇:
>>>
>>> from collections import deque >>> q = deque() >>> q.append("eat") >>> q.append("sleep") >>> q.append("code") >>> q deque(['eat', 'sleep', 'code']) >>> q.popleft() 'eat' >>> q.popleft() 'sleep' >>> q.popleft() 'code' >>> q.popleft() Traceback (most recent call last): File "
queue.Queue: 并行計(jì)算的鎖定語(yǔ)義
queue.QueuePython 標(biāo)準(zhǔn)庫(kù)中的實(shí)現(xiàn)是同步的,并提供鎖定語(yǔ)義以支持多個(gè)并發(fā)生產(chǎn)者和消費(fèi)者。
該queue模塊包含其他幾個(gè)實(shí)現(xiàn)多生產(chǎn)者、多消費(fèi)者隊(duì)列的類,這些隊(duì)列對(duì)并行計(jì)算很有用。
根據(jù)您的用例,鎖定語(yǔ)義可能會(huì)有所幫助,或者只會(huì)產(chǎn)生不必要的開(kāi)銷(xiāo)。在這種情況下,您最好將其collections.deque用作通用隊(duì)列:
>>>
>>> from queue import Queue >>> q = Queue() >>> q.put("eat") >>> q.put("sleep") >>> q.put("code") >>> q
multiprocessing.Queue: 共享作業(yè)隊(duì)列
multiprocessing.Queue是一個(gè)共享的作業(yè)隊(duì)列實(shí)現(xiàn),允許多個(gè)并發(fā)工作人員并行處理排隊(duì)的項(xiàng)目。基于進(jìn)程的并行化在 CPython 中很流行,因?yàn)槿纸忉屍麈i(GIL) 可以防止在單個(gè)解釋器進(jìn)程上進(jìn)行某些形式的并行執(zhí)行。
作為用于在進(jìn)程之間共享數(shù)據(jù)的專用隊(duì)列實(shí)現(xiàn),multiprocessing.Queue可以輕松地跨多個(gè)進(jìn)程分發(fā)工作以解決 GIL 限制。這種類型的隊(duì)列可以跨進(jìn)程邊界存儲(chǔ)和傳輸任何可腌制的對(duì)象:
>>>
>>> from multiprocessing import Queue >>> q = Queue() >>> q.put("eat") >>> q.put("sleep") >>> q.put("code") >>> q
Python 中的隊(duì)列:總結(jié)
Python 包括幾個(gè)隊(duì)列實(shí)現(xiàn)作為核心語(yǔ)言及其標(biāo)準(zhǔn)庫(kù)的一部分。
list?對(duì)象可以用作隊(duì)列,但由于性能緩慢,通常不建議這樣做。
如果您不是在尋找并行處理支持,那么提供的collections.deque實(shí)現(xiàn)是在 Python 中實(shí)現(xiàn) FIFO 隊(duì)列數(shù)據(jù)結(jié)構(gòu)的絕佳默認(rèn)選擇。它提供了您期望從一個(gè)好的隊(duì)列實(shí)現(xiàn)中獲得的性能特征,也可以用作堆棧(LIFO 隊(duì)列)。
優(yōu)先隊(duì)列
一個(gè)優(yōu)先級(jí)隊(duì)列是管理一組與記錄的容器數(shù)據(jù)結(jié)構(gòu)完全有序鍵提供快速訪問(wèn)記錄中,設(shè)定的最小或最大關(guān)鍵。
您可以將優(yōu)先級(jí)隊(duì)列視為修改后的隊(duì)列。它不是按插入時(shí)間檢索下一個(gè)元素,而是檢索最高優(yōu)先級(jí)的元素。各個(gè)元素的優(yōu)先級(jí)由應(yīng)用于它們的鍵的順序決定。
優(yōu)先級(jí)隊(duì)列通常用于處理調(diào)度問(wèn)題。例如,您可以使用它們來(lái)優(yōu)先處理具有更高緊迫性的任務(wù)。
想想操作系統(tǒng)任務(wù)調(diào)度程序的工作:
理想情況下,系統(tǒng)上優(yōu)先級(jí)較高的任務(wù)(例如玩實(shí)時(shí)游戲)應(yīng)優(yōu)先于優(yōu)先級(jí)較低的任務(wù)(例如在后臺(tái)下載更新)。通過(guò)將待處理的任務(wù)組織在以任務(wù)緊迫性為關(guān)鍵的優(yōu)先級(jí)隊(duì)列中,任務(wù)調(diào)度器可以快速選擇優(yōu)先級(jí)最高的任務(wù)并讓它們先運(yùn)行。
在本節(jié)中,您將看到一些關(guān)于如何使用 Python 標(biāo)準(zhǔn)庫(kù)中包含的內(nèi)置數(shù)據(jù)結(jié)構(gòu)或數(shù)據(jù)結(jié)構(gòu)在 Python 中實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列的選項(xiàng)。每個(gè)實(shí)現(xiàn)都有自己的優(yōu)點(diǎn)和缺點(diǎn),但在我看來(lái),大多數(shù)常見(jiàn)場(chǎng)景都有明顯的贏家。讓我們找出它是哪一個(gè)。
list: 手動(dòng)排序隊(duì)列
您可以使用 sortedlist來(lái)快速識(shí)別和刪除最小或最大元素。缺點(diǎn)是將新元素插入列表是一個(gè)緩慢的O?(?n?) 操作。
雖然在標(biāo)準(zhǔn)庫(kù)中可以在O?(log?n?) 時(shí)間內(nèi)找到插入點(diǎn)bisect.insort,但這總是由緩慢的插入步驟決定。
通過(guò)附加到列表和重新排序來(lái)維護(hù)順序也至少需要O?(?n?log?n?) 時(shí)間。另一個(gè)缺點(diǎn)是您必須在插入新元素時(shí)手動(dòng)處理列表的重新排序。錯(cuò)過(guò)這一步很容易引入錯(cuò)誤,而負(fù)擔(dān)始終落在開(kāi)發(fā)人員身上。
這意味著排序列表僅適用于插入很少的優(yōu)先級(jí)隊(duì)列:
>>>
>>> q = [] >>> q.append((2, "code")) >>> q.append((1, "eat")) >>> q.append((3, "sleep")) >>> # Remember to re-sort every time a new element is inserted, >>> # or use bisect.insort() >>> q.sort(reverse=True) >>> while q: ... next_item = q.pop() ... print(next_item) ... (1, 'eat') (2, 'code') (3, 'sleep')
heapq: 基于列表的二叉堆
heapq是一個(gè)二進(jìn)制堆實(shí)現(xiàn),通常由一個(gè)普通的list支持,它支持在O?(log?n?) 時(shí)間內(nèi)插入和提取最小元素。
該模塊是在 Python 中實(shí)現(xiàn)優(yōu)先級(jí)隊(duì)列的不錯(cuò)選擇。由于heapq技術(shù)上僅提供最小堆實(shí)現(xiàn),因此必須采取額外的步驟來(lái)確保排序穩(wěn)定性和實(shí)際優(yōu)先級(jí)隊(duì)列通常期望的其他功能:
>>>
>>> import heapq >>> q = [] >>> heapq.heappush(q, (2, "code")) >>> heapq.heappush(q, (1, "eat")) >>> heapq.heappush(q, (3, "sleep")) >>> while q: ... next_item = heapq.heappop(q) ... print(next_item) ... (1, 'eat') (2, 'code') (3, 'sleep')
queue.PriorityQueue: 漂亮的優(yōu)先隊(duì)列
queue.PriorityQueue在heapq內(nèi)部使用并共享相同的時(shí)間和空間復(fù)雜性。不同之處在于PriorityQueue同步并提供鎖定語(yǔ)義以支持多個(gè)并發(fā)生產(chǎn)者和消費(fèi)者。
根據(jù)您的用例,這可能會(huì)有所幫助,或者它可能只是稍微減慢您的程序速度。在任何情況下,您可能更喜歡 提供的基于類的接口而PriorityQueue不是提供的基于函數(shù)的接口heapq:
>>>
>>> from queue import PriorityQueue >>> q = PriorityQueue() >>> q.put((2, "code")) >>> q.put((1, "eat")) >>> q.put((3, "sleep")) >>> while not q.empty(): ... next_item = q.get() ... print(next_item) ... (1, 'eat') (2, 'code') (3, 'sleep')
Python 中的優(yōu)先隊(duì)列:總結(jié)
Python 包括幾個(gè)可供您使用的優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)。
queue.PriorityQueue以一個(gè)漂亮的面向?qū)ο蟮慕缑婧鸵粋€(gè)明確說(shuō)明其意圖的名稱脫穎而出。它應(yīng)該是您的首選。
如果您想避免 的鎖定開(kāi)銷(xiāo)queue.PriorityQueue,那么heapq直接使用該模塊也是一個(gè)不錯(cuò)的選擇。
結(jié)論:Python 數(shù)據(jù)結(jié)構(gòu)
您對(duì) Python 中常見(jiàn)數(shù)據(jù)結(jié)構(gòu)的瀏覽到此結(jié)束。憑借您在此處獲得的知識(shí),您已準(zhǔn)備好實(shí)施適合您的特定算法或用例的高效數(shù)據(jù)結(jié)構(gòu)。
在本教程中,您學(xué)習(xí)了:
Python標(biāo)準(zhǔn)庫(kù)中內(nèi)置了哪些常見(jiàn)的抽象數(shù)據(jù)類型
最常見(jiàn)的抽象數(shù)據(jù)類型如何映射到 Python 的命名方案
如何在各種算法中實(shí)際使用抽象數(shù)據(jù)類型
如果您喜歡從Python Tricks 的這個(gè)示例中學(xué)到的東西,那么一定要查看本書(shū)的其余部分。
Python 數(shù)據(jù)結(jié)構(gòu)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。