【万字长文详解】Python库collections,让你击败99%的Pythoner
Python的collections庫實現了特定目標的容器,以提供Python標準內建容器 dict , list , set , 和 tuple 的替代選擇。
為很多用其他方法很難實現的場景提供了解決方案。本文我們將會學習該模塊的抽象概念是如何產生的,日后處理不同問題的過程中遲早會用得到這些知識。
免責聲明:這篇文章是關于Python的一個相當高級的特性。如果你剛入門,建議先收藏,請等一等再學!
一、模塊概述
1、模塊作用
官方說法:collections模塊實現了特定目標的容器,以提供Python標準內建容器dict ,list , set , 和tuple的替代選擇。
通俗說法:Python內置的數據類型和方法,collections模塊在這些內置類型的基礎提供了額外的高性能數據類型,比如基礎的字典是不支持順序的,collections模塊的OrderedDict類構建的字典可以支持順序,collections模塊的這些擴展的類用處非常大,熟練掌握該模塊,可以大大簡化Python代碼,提高Python代碼逼格和效率,高手入門必備。
2、模塊資料
關于該模塊,官方的參考資料寫的非常詳細,也很有價值,大家可以參考
中文文檔:https://docs.python.org/zh-cn/3/library/collections.html#module-collections
英文文檔:https://docs.python.org/3/library/collections.html#module-collections
3、模塊子類
用collections.__all__查看所有的子類,一共包含9個
import collections print(collections.__all__) ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList', 'UserString', 'Counter', 'OrderedDict', 'ChainMap']這個模塊實現了特定目標的容器,以提供Python標準內建容器dict , list , set , 和tuple 的替代選擇。
namedtuple() | 創建命名元組子類的工廠函數,生成可以使用名字來訪問元素內容的tuple子類 |
deque | 類似列表(list)的容器,實現了在兩端快速添加(append)和彈出(pop) |
ChainMap | 類似字典(dict)的容器類,將多個映射集合到一個視圖里面 |
Counter | 字典的子類,提供了可哈希對象的計數功能 |
OrderedDict | 字典的子類,保存了他們被添加的順序,有序字典 |
defaultdict | 字典的子類,提供了一個工廠函數,為字典查詢提供一個默認值 |
UserDict | 封裝了字典對象,簡化了字典子類化 |
UserList | 封裝了列表對象,簡化了列表子類化 |
UserString | 封裝了字符串對象,簡化了字符串子類化(中文版翻譯有誤) |
?
二、計數器-Counter
1、基礎介紹
一個計數器工具提供快速和方便的計數,Counter是一個dict的子類,用于計數可哈希對象。它是一個集合,元素像字典鍵(key)一樣存儲,它們的計數存儲為值。計數可以是任何整數值,包括0和負數,Counter類有點像其他語言中的bags或multisets。簡單說,就是可以統計計數,來幾個例子看看就清楚了,比如
#計算top10的單詞 from collections import Counter import re text = 'remove an existing key one level down remove an existing key one level down' words = re.findall(r'\w+', text) Counter(words).most_common(10) [('remove',?2),('an',?2),('existing',?2),('key',?2),('one',?2)('level',?2),('down',?2)]? #計算列表中單詞的個數 cnt = Counter() for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:cnt[word] += 1 cnt Counter({'red':?2,?'blue':?3,?'green':?1}) 上述這樣計算有點嘛,下面的方法更簡單,直接計算就行 L = ['red', 'blue', 'red', 'green', 'blue', 'blue'] Counter(L) Counter({'red':?2,?'blue':?3,?'green':?1} 元素從一個iterable 被計數或從其他的mapping (or counter)初始化: from?collections?import?Counter #字符串計數 Counter('gallahad') Counter({'g':?1,?'a':?3,?'l':?2,?'h':?1,?'d':?1}) #字典計數 Counter({'red': 4, 'blue': 2}) Counter({'red':?4,?'blue':?2}) #是個啥玩意計數 Counter(cats=4, dogs=8) Counter({'cats':?4,?'dogs':?8}) Counter(['red', 'blue', 'red', 'green', 'blue', 'blue']) Counter({'red': 2, 'blue': 3, 'green': 1計數器對象除了字典方法以外,還提供了三個其他的方法:
1、elements()
描述:返回一個迭代器,其中每個元素將重復出現計數值所指定次。元素會按首次出現的順序返回。如果一個元素的計數值小于1,elements()?將會忽略它。
語法:elements( ?)
參數:無
c = Counter(a=4, b=2, c=0, d=-2) list(c.elements()) ['a', 'a', 'a', 'a', 'b', 'b'] sorted(c.elements()) ['a', 'a', 'a', 'a', 'b', 'b'] c = Counter(a=4, b=2, c=0, d=5) list(c.elements()) ['a',?'a',?'a',?'a',?'b',?'b',?'d',?'d',?'d',?'d',?'d']2、most_common()
返回一個列表,其中包含n個最常見的元素及出現次數,按常見程度由高到低排序。如果 n 被省略或為None,most_common()?將返回計數器中的所有元素,計數值相等的元素按首次出現的順序排序,經常用來計算top詞頻的詞語。
Counter('abracadabra').most_common(3) [('a', 5), ('b', 2), ('r', 2)] Counter('abracadabra').most_common(5) [('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]3、subtract()
從迭代對象或映射對象減去元素。像dict.update()?但是是減去,而不是替換。輸入和輸出都可以是0或者負數。
c = Counter(a=4, b=2, c=0, d=-2) d = Counter(a=1, b=2, c=3, d=4) c.subtract(d) c Counter({'a':?3,?'b':?0,?'c':?-3,?'d':?-6}) #減去一個abcd str0 = Counter('aabbccdde') str0 Counter({'a':?2,?'b':?2,?'c':?2,?'d':?2,?'e':?1}) str0.subtract('abcd') str0 Counter({'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1}4、字典方法
通常字典方法都可用于Counter對象,除了有兩個方法工作方式與字典并不相同。
fromkeys(iterable)
這個類方法沒有在Counter中實現。
update([iterable-or-mapping])
從迭代對象計數元素或者從另一個映射對象?(或計數器) 添加。像 dict.update()?但是是加上,而不是替換。另外,迭代對象應該是序列元素,而不是一個?(key, value)?對。
sum(c.values()) # total of all counts c.clear() # reset all counts list(c) # list unique elements set(c) # convert to a set dict(c) # convert to a regular dictionary c.items() # convert to a list of (elem, cnt) pairs Counter(dict(list_of_pairs)) # convert from a list of (elem, cnt) pairs c.most_common()[:-n-1:-1] # n least common elements +c # remove zero and negative counts5、數學操作
這個功能非常強大,提供了幾個數學操作,可以結合 Counter 對象,以生產 multisets (計數器中大于0的元素)。加和減,結合計數器,通過加上或者減去元素的相應計數。交集和并集返回相應計數的最小或最大值。每種操作都可以接受帶符號的計數,但是輸出會忽略掉結果為零或者小于零的計數。
c = Counter(a=3, b=1) d = Counter(a=1, b=2) c + d # add two counters together: c[x] + d[x] Counter({'a': 4, 'b': 3}) c - d # subtract (keeping only positive counts) Counter({'a': 2}) c & d # interp: min(c[x], d[x]) Counter({'a': 1, 'b': 1}) c | d # union: max(c[x], d[x]) Counter({'a': 3, 'b': 2})單目加和減(一元操作符)意思是從空計數器加或者減去。
寫一個計算文本相似的算法,加權相似
三、雙向隊列-deque
雙端隊列,可以快速的從另外一側追加和推出對象,deque是一個雙向鏈表,針對list連續的數據結構插入和刪除進行優化。它提供了兩端都可以操作的序列,這表示在序列的前后你都可以執行添加或刪除操作。雙向隊列(deque)對象支持以下方法:
1、append()
添加 x 到右端。
d = deque('ghi') d.append('j') d deque(['g', 'h', 'i', 'j'])2、appendleft()
添加 x 到左端。
d.appendleft('f') d deque(['f', 'g', 'h', 'i', 'j'])3、clear()
移除所有元素,使其長度為0.
d = deque('ghi') d.clear() d deque([])4、copy()
創建一份淺拷貝。
d = deque('xiaoweuge') y = d.copy() print(y) deque(['x', 'i', 'a', 'o', 'w', 'e', 'u', 'g', 'e'])5、count()
計算 deque 中元素等于?x?的個數。
d = deque('xiaoweuge-shuai') d.count('a') 26、extend()
擴展deque的右側,通過添加iterable參數中的元素。
a = deque('abc') b = deque('cd') a.extend(b) a deque(['a',?'b',?'c',?'c',?'d']) 與append 的區別 a = deque('abc') b = deque('cd') a.append(b) deque(['a', 'b', 'c', deque(['c', 'd'])])7、extendleft()
擴展deque的左側,通過添加iterable參數中的元素。注意,左添加時,在結果中iterable參數中的順序將被反過來添加。
a = deque('abc') b = deque('cd') a.extendleft(b) a deque(['d', 'c', 'a', 'b', 'c'])8、index()
返回 x 在 deque 中的位置(在索引 start 之后,索引 stop 之前)。返回第一個匹配項,如果未找到則引發 ValueError。
3.5 新版功能.
d = deque('xiaoweuge') d.index('w') 49、insert()
在位置 i 插入 x 。
如果插入會導致一個限長 deque 超出長度 maxlen 的話,就引發一個 IndexError。
a = deque('abc') a.insert(1,'X') deque(['a', 'X', 'b', 'c'])10、pop()
移去并且返回一個元素,deque 最右側的那一個。如果沒有元素的話,就引發一個
IndexError。 d.pop() 'j'11、popleft()
移去并且返回一個元素,deque 最左側的那一個。如果沒有元素的話,就引發 IndexError。
d.popleft() 'f'12、remove(value)
移除找到的第一個 value。如果沒有的話就引發 ValueError。 a = deque('abca') a.remove('a') a deque(['b', 'c', 'a'])13、reverse()
將deque逆序排列。返回 None 。
#逆序排列 d = deque('ghi') # 創建一個deque list(reversed(d)) ['i', 'h', 'g']deque(reversed(d)) deque(['i', 'h', 'g'])14、rotate(n=1)
向右循環移動 n 步。如果 n 是負數,就向左循環。
如果deque不是空的,向右循環移動一步就等價于 d.appendleft(d.pop())?, 向左循環一步就等價于 d.append(d.popleft())?。
# 向右邊擠一擠 d = deque('ghijkl') d.rotate(1) d deque(['l',?'g',?'h',?'i',?'j',?'k']) # 向左邊擠一擠 d.rotate(-1) d deque(['g',?'h',?'i',?'j',?'k',?'l']) #看一個更明顯的 x = deque('12345') x deque(['1', '2', '3', '4', '5']) x.rotate() x deque(['5',?'1',?'2',?'3',?'4']) d?=?deque(['12','av','cd']) d.rotate(1) deque(['cd', '12', 'av']15、maxlen
Deque的最大尺寸,如果沒有限定的話就是 None 。
from collections import deque d=deque(maxlen=10) for i in range(20):d.append(i) d deque([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])除了以上操作,deque還支持迭代、封存、len(d)、reversed(d)、copy.deepcopy(d)、copy.copy(d)、成員檢測運算符 in 以及下標引用例如通過 d[0]?訪問首個元素等。索引訪問在兩端的復雜度均為 O(1) 但在中間則會低至 O(n)。如需快速隨機訪問,請改用列表。
Deque從版本3.5開始支持?__add__(), __mul__(), 和?__imul__()?。
from collections import deque d?=?deque('ghi')?????????????????#?創建一個deque for elem in d:print(elem.upper()) G H I #從右邊添加一個元素 d.append('j') d deque(['g',?'h',?'i',?'j']) #從左邊添加一個元素 d.appendleft('f') d deque(['f',?'g',?'h',?'i',?'j']) #右邊刪除 d.pop() 'j' #左邊邊刪除 d.popleft() 'f' #看看還剩下啥 list(d) # ['g', 'h', 'i'] #成員檢測 'h' in d True #添加多個元素 d.extend('jkl') d deque(['g',?'h',?'i',?'j',?'k',?'l']) d.clear() # empty the deque d.pop() # cannot pop from an empty deque Traceback (most recent call last):File "<pyshell#6>", line 1, in -toplevel-d.pop() IndexError:?pop?from?an?empty?deque d.extendleft('abc') # extendleft() reverses the input order d deque(['c', 'b', 'a'])四、有序字典-OrderedDict?
有序詞典就像常規詞典一樣,但有一些與排序操作相關的額外功能,popitem()?方法有不同的簽名。它接受一個可選參數來指定彈出哪個元素。move_to_end()?方法,可以有效地將元素移動到任一端。
有序詞典就像常規詞典一樣,但有一些與排序操作相關的額外功能。由于內置的 dict 類獲得了記住插入順序的能力(在 Python 3.7 中保證了這種新行為),它們變得不那么重要了。
一些與 dict 的不同仍然存在:
常規的 dict 被設計為非常擅長映射操作。跟蹤插入順序是次要的。
OrderedDict 旨在擅長重新排序操作。空間效率、迭代速度和更新操作的性能是次要的。
算法上, OrderedDict 可以比 dict 更好地處理頻繁的重新排序操作。這使其適用于跟蹤最近的訪問(例如在?LRU cache?中)。
對于 OrderedDict ,相等操作檢查匹配順序。
OrderedDict 類的 popitem()?方法有不同的簽名。它接受一個可選參數來指定彈出哪個元素。
OrderedDict 類有一個 move_to_end()?方法,可以有效地將元素移動到任一端。
Python 3.8之前, dict 缺少?__reversed__()?方法。
?
傳統字典方法 | OrderedDict方法 | 差異 |
?clear | ?clear | |
?copy | ?copy | |
?fromkeys | ?fromkeys | |
?get | ?get | |
?items | ?items | |
?keys | ?keys | |
?pop | ?pop | |
?popitem | ?popitem | OrderedDict ? 類的 popitem() 方法有不同的簽名。它接受一個可選參數來指定彈出哪個元素。 |
?setdefault | ?setdefault | |
?update | ?update | |
?values | ?values | |
?move_to_end | 可以有效地將元素移動到任一端。 |
1、popitem
語法:popitem(last=True)
功能:有序字典的 popitem()?方法移除并返回一個 (key, value) 鍵值對。如果 last 值為真,則按 LIFO 后進先出的順序返回鍵值對,否則就按 FIFO 先進先出的順序返回鍵值對。
?from?collections?import?OrderedDict d = OrderedDict.fromkeys('abcde') d.popitem()('e', None) d OrderedDict([('a', None), ('b', None), ('c', None), ('d', None)]) #last=False時,彈出第一個 d = OrderedDict.fromkeys('abcde') ''.join(d.keys()) 'abcde' d.popitem(last=False) ''.join(d.keys()) 'bcde'2、move_to_end
from collections import OrderedDict d = OrderedDict.fromkeys('abcde') d.move_to_end('b') ''.join(d.keys()) 'acdeb' d OrderedDict([('a',?None),?('c',?None),?('d',?None),?('e',?None),?('b',?None)]) d.move_to_end('b', last=False) ''.join(d.keys()) 'bacde'3、reversed()
相對于通常的映射方法,有序字典還另外提供了逆序迭代的支持,通過reversed()?。
d = OrderedDict.fromkeys('abcde') list(reversed(d))['e', 'd', 'c', 'b', 'a']五、可命名元組-namedtuple
生成可以使用名字來訪問元素內容的tuple子類,命名元組賦予每個位置一個含義,提供可讀性和自文檔性。它們可以用于任何普通元組,并添加了通過名字獲取值的能力,通過索引值也是可以的。
1、參數介紹
namedtuple(typename,field_names,*,verbose=False, rename=False,?module=None)
1)typename:該參數指定所創建的tuple子類的類名,相當于用戶定義了一個新類。
2)field_names:該參數是一個字符串序列,如?['x','y']。此外,field_names 也可直接使用單個字符串代表所有字段名,多個字段名用空格、逗號隔開,如?'x y'?或?'x,y'。任何有效的?Python?標識符都可作為字段名(不能以下畫線開頭)。有效的標識符可由字母、數字、下畫線組成,但不能以數字、下面線開頭,也不能是關鍵字(如 return、global、pass、raise 等)。
3)rename:如果將該參數設為 True,那么無效的字段名將會被自動替換為位置名。例如指定?['abc','def','ghi','abc'],它將會被替換為?['abc', '_1','ghi','_3'],這是因為 def 字段名是關鍵字,而 abc 字段名重復了。
4)verbose:如果該參數被設為 True,那么當該子類被創建后,該類定義就被立即打印出來。
5)module:如果設置了該參數,那么該類將位于該模塊下,因此該自定義類的?__module__?屬性將被設為該參數值。
?
2、應用案例
1)水族箱案例
Python元組是一個不可變的,或不可改變的,有序的元素序列。元組經常用來表示縱列數據;例如,一個CSV文件中的行數或一個SQL數據庫中的行數。一個水族箱可以用一系列元組來記錄它的魚類的庫存。
一個單獨的魚類元組:
這個元組由三個字符串元素組成。
雖然在某些方面很有用,但是這個元組并沒有清楚地指明它的每個字段代表什么。實際上,元素0是一個名稱,元素1是一個物種,元素2是一個飼養箱。
魚類元組字段說明:
這個表清楚地表明,該元組的三個元素都有明確的含義。
來自collections模塊的namedtuple允許你向一個元組的每個元素添加顯式名稱,以便在你的Python程序中明確這些元素的含義。
讓我們使用namedtuple來生成一個類,從而明確地命名魚類元組的每個元素:
from collections import namedtuple可以讓你的Python程序訪問namedtuple工廠函數。namedtuple()函數調用會返回一個綁定到名稱Fish的類。namedtuple()函數有兩個參數:我們的新類“Fish”的期望名稱和命名元素["name"、"species”、“tank"]的一個列表。
我們可以使用Fish類來表示前面的魚類元組:
如果我們運行這段代碼,我們將看到以下輸出:
sammy是使用Fish類進行實例化的。sammy是一個具有三個明確命名元素的元組。
sammy的字段可以通過它們的名稱或者一個傳統的元組索引來訪問:
如果我們運行這兩個print調用,我們將看到以下輸出:
訪問.species會返回與使用[1]訪問sammy的第二個元素相同的值。
使用collections模塊中的namedtuple可以在維護元組(即它們是不可變的、有序的)的重要屬性的同時使你的程序更具可讀性。
此外,namedtuple工廠函數還會向Fish實例添加幾個額外的方法。
使用._asdict()將一個實例轉換為字典:
如果我們運行print,你會看到如下輸出:
在sammy上調用.asdict()將返回一個字典,該字典會將三個字段名稱分別映射到它們對應的值。
大于3.8的Python版本輸出這一行的方式可能略有不同。例如,你可能會看到一個OrderedDict,而不是這里顯示的普通字典。
2)加法器案例
from collections import namedtuple # 定義命名元組類:Point Point = namedtuple('Point', ['x', 'y']) # 初始化Point對象,即可用位置參數,也可用命名參數 p = Point(11, y=22) # 像普通元組一樣用根據索引訪問元素 print(p[0] + p[1]) 33 #執行元組解包,按元素的位置解包 a, b = p print(a, b) 11, 22 #根據字段名訪問各元素 print(p.x + p.y) 33 print(p) Point(x=11, y=22)3、三個方法
備注: 在Python中,帶有前導下劃線的方法通常被認為是“私有的”。但是,namedtuple提供的其他方法(如._asdict()、._make()、._replace()等)是公開的。
除了繼承元組的方法,命名元組還支持三個額外的方法和兩個屬性。為了防止字段名沖突,方法和屬性以下劃線開始。
_make(iterable)
類方法從存在的序列或迭代實例創建一個新實例。
t = [14, 55] Point._make(t)_asdict()
返回一個新的 dict ,它將字段名稱映射到它們對應的值:
p = Point(x=11, y=22) p._asdict() OrderedDict([('x', 11), ('y', 22)])_replace(**kwargs)
返回一個新的命名元組實例,并將指定域替換為新的值
p = Point(x=11, y=22) p._replace(x=33) Point(x=33, y=22)4、兩個屬性
_fields
字符串元組列出了字段名。用于提醒和從現有元組創建一個新的命名元組類型。
p._fields # view the field names ('x', 'y') Color = namedtuple('Color', 'red green blue') Pixel = namedtuple('Pixel', Point._fields + Color._fields) Pixel(11, 22, 128, 255, 0) Pixel(x=11, y=22, red=128, green=255, blue=0)_field_defaults
字典將字段名稱映射到默認值。
Account = namedtuple('Account', ['type', 'balance'], defaults=[0]) Account._field_defaults {'balance': 0} Account('premium') Account(type='premium', balance=0)5、其他函數
getattr()
要獲取這個名字域的值,使用?getattr()?函數 :
getattr(p, 'x') 11轉換一個字典到命名元組,使用 ** 兩星操作符
因為一個命名元組是一個正常的Python類,它可以很容易的通過子類更改功能。這里是如何添加一個計算域和定寬輸出打印格式:
?
六、默認字典-defaultdict
在Python字典中收集數據通常是很有用的。
在字典中獲取一個 key 有兩種方法, 第一種 get , 第二種 通過 [] 獲取.
使用dict時,如果引用的Key不存在,就會拋出KeyError。如果希望key不存在時,返回一個默認值,就可以用defaultdict。
當我使用普通的字典時,用法一般是dict={},添加元素的只需要dict[element] =value即,調用的時候也是如此,dict[element] = xxx,但前提是element字典里,如果不在字典里就會報錯
這時defaultdict就能排上用場了,defaultdict的作用是在于,當字典里的key不存在但被查找時,返回的不是keyError而是一個默認值,這個默認值是什么呢,下面會說
1、基礎介紹
defaultdict([default_factory[,?...]])
返回一個新的類似字典的對象。defaultdict是內置dict類的子類。它重載了一個方法并添加了一個可寫的實例變量。其余的功能與dict類相同,此處不再重復說明。
本對象包含一個名為default_factory的屬性,構造時,第一個參數用于為該屬性提供初始值,默認為?None。所有其他參數(包括關鍵字參數)都相當于傳遞給 dict 的構造函數。
defaultdict?對象除了支持標準?dict?的操作,還支持以下方法作為擴展:
__missing__(key)
如果 default_factory 屬性為 None,則調用本方法會拋出 KeyError 異常,附帶參數 key。
如果?default_factory?不為?None,則它會被(不帶參數地)調用來為?key?提供一個默認值,這個值和?key?作為一對鍵值對被插入到字典中,并作為本方法的返回值返回。
如果調用 default_factory 時拋出了異常,這個異常會原封不動地向外層傳遞。
在無法找到所需鍵值時,本方法會被 dict 中的?__getitem__()?方法調用。無論本方法返回了值還是拋出了異常,都會被?__getitem__()?傳遞。
注意,__missing__()?不會?被?__getitem__()?以外的其他方法調用。意味著 get()?會像正常的 dict 那樣返回 None,而不是使用 default_factory。
2、示例介紹
使用?list?作為?default_factory,很輕松地將(鍵-值對組成的)序列轉換為(鍵-列表組成的)字典
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)] d = defaultdict(list) for k, v in s:d[k].append(v) sorted(d.items()) [('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]當每個鍵第一次遇見時,它還沒有在字典里面,所以自動創建該條目,即調用default_factory方法,返回一個空的 list。list.append()?操作添加值到這個新的列表里。當再次存取該鍵時,就正常操作,list.append()?添加另一個值到列表中。這個計數比它的等價方法dict.setdefault()要快速和簡單:
設置 default_factory為int,使defaultdict用于計數(類似其他語言中的 bag或multiset):
defaultdict絕不會引發一個KeyError。如果一個鍵不存在,defaultdict會插入并返回一個占位符值來代替:
如果我們運行這段代碼,我們將看到如下輸出:
defaultdict會插入并返回一個占位符值,而不是引發一個KeyError。在本例中,我們將占位符值指定為一個列表。
相比之下,常規字典會在缺失的鍵上引發一個KeyError:
如果我們運行這段代碼,我們將看到如下輸出:
當我們試圖訪問一個不存在的鍵時,常規字典my_regular_dict會引發一個KeyError。
defaultdict的行為與常規字典不同。defaultdict會不帶任何參數調用占位符值來創建一個新對象,而不是在缺失的鍵上引發一個KeyError。在本例中,是調用list()創建一個空列表。
繼續我們虛構的水族箱示例,假設我們有一個表示水族箱清單的魚類元組列表:
?
水族箱中有三種魚——它們的名字、種類和飼養箱在這三個元組中都有指出。
我們的目標是按飼養箱組織我們的清單—我們想知道每個飼養箱中存在的魚的列表。換句話說,我們需要一個能將“tank-a”映射到["Jamie", "Mary"] ,并且將“tank-b”映射到["Jamie"]的字典。
我們可以使用defaultdict來按飼養箱對魚進行分組:
運行這段代碼,我們將看到以下輸出:
fish_names_by_tank被聲明為一個defaultdict,它默認會插入list()而不是引發一個KeyError。由于這保證了fish_names_by_tank中的每個鍵都將指向一個list,所以我們可以自由地調用.append()來將名稱添加到每個飼養箱的列表中。
這里,defaultdict幫助你減少了出現未預期的KeyErrors的機會。減少未預期的KeyErrors意味著你可以用更少的行更清晰地編寫你的程序。更具體地說,defaultdict習慣用法讓你避免了手動地為每個飼養箱實例化一個空列表。
如果沒有 defaultdict, for循環體可能看起來更像這樣:
使用常規字典(而不是defaultdict)意味著for循環體總是必須檢查fish_names_by_tank中給定的tank是否存在。只有在驗證了fish_names_by_tank中已經存在tank,或者已經使用一個[]初始化了tank之后,我們才可以添加魚類名稱。
在填充字典時,defaultdict可以幫助我們減少樣板代碼,因為它從不引發KeyError。
?
七、映射鏈-ChainMap
1、ChainMap是什么
ChainMap最基本的使用,可以用來合并兩個或者更多個字典,當查詢的時候,從前往后依次查詢。
ChainMap:將多個字典視為一個,解鎖Python超能力。
ChainMap是由Python標準庫提供的一種數據結構,允許你將多個字典視為一個。換句話說:ChainMap是一個基于多dict的可更新的視圖,它的行為就像一個普通的dict。
ChainMap類用于快速鏈接多個映射,以便將它們視為一個單元。它通常比創建新字典和多次調用update()快得多。
你以前可能從來沒有聽說過ChainMap,你可能會認為ChainMap的使用情況是非常特定的。坦率地說,你是對的。
我知道的用例包括:
通過多個字典搜索
提供鏈缺省值
經常計算字典子集的性能關鍵的應用程序
2、特性
1)找到一個就不找了:這個列表是按照第一次搜索到最后一次搜索的順序組織的,搜索查詢底層映射,直到一個鍵被找到。
2)更新原始映射:不同的是,寫,更新和刪除只操作第一個映射。
3)支持所有常用字典方法。
?
簡而言之ChainMap:將多個字典視為一個,解鎖Python超能力。
Python標準庫中的集合模塊包含許多為性能而設計的實用的數據結構。著名的包括命名元組或計數器。
今天,通過實例,我們來看看鮮為人知的ChainMap。通過瀏覽具體的示例,我希望給你一個提示,關于在更高級的Python工作中使用ChainMap將如何從中受益。
3、應用案例-基礎案例
from collections import ChainMap baseline = {'music': 'bach', 'art': 'rembrandt'} adjustments = {'art': 'van gogh', 'opera': 'carmen'} ChainMap(adjustments, baseline) ChainMap({'art': 'van gogh', 'opera': 'carmen'}, {'music': 'bach', 'art': 'rembrandt'}) list(ChainMap(adjustments, baseline)) ['music', 'art', 'opera'] #存在重復元素時,也不會去重 dcic1 = {'label1': '11', 'label2': '22'} dcic2 = {'label2': '22', 'label3': '33'} dcic3 = {'label4': '44', 'label5': '55'} last = ChainMap(dcic1, dcic2,dcic3) last ChainMap({'label1': '11', 'label2': '22'}, {'label2': '22', 'label3': '33'}, {'label4': '44', 'label5': '55'})new_child()方法
用法:new_child(m=None)
返回一個新的ChainMap類,包含了一個新映射(map),后面跟隨當前實例的全部映射map。如果m被指定,它就成為不同新的實例,就是在所有映射前加上 m,如果沒有指定,就加上一個空字典,這樣的話一個 d.new_child()?調用等價于ChainMap({}, *d.maps)?。這個方法用于創建子上下文,不改變任何父映射的值。
last.new_child(m={'key_new':888}) ChainMap({'key_new': 888}, {'label1': '11', 'label2': '22'}, {'label2': '22','label3': '33'}, {'label4': '44', 'label5': '55'})parents屬性
屬性返回一個新的ChainMap包含所有的當前實例的映射,除了第一個。這樣可以在搜索的時候跳過第一個映射。使用的場景類似在?nested scopes?嵌套作用域中使用nonlocal關鍵詞。用例也可以類比內建函數super()?。一個d.parents 的引用等價于ChainMap(*d.maps[1:])?。
last.parents ChainMap({'label2': '22', 'label3': '33'}, {'label4': '44', 'label5': '55'})4、應用案例-購物清單
作為使用ChainMap的第一個例子,讓我們考慮一張購物清單。我們的清單可能包含玩具,電腦,甚至衣服。所有這些條目都有價格,所以我們將把我們的條目存儲在名稱價格映射中。
toys = {'Blocks':30,'Monopoly':20} computers = {'iMac':1000,'Chromebook':1000,'PC':400} clothing = {'Jeans':40,'T-shirt':10}現在我們可以使用ChainMap在這些不同的集合上建立一個單一的視圖:
這使得我們可以查詢清單,就像它是一個單一的字典:
正如官方文檔所述,ChainMap支持所有常用的字典方法。我們可以使用.get()來搜索可能不存在的條目,或者使用 .pop()刪除條目。
如果我們現在把玩具添加到toys字典里,它也將在清單中可用。這是ChainMap的可更新的方面。
toys['Nintendo'] = 20 inventory['Nintendo'] 20Oh和ChainMap有一個恰當的字符串表示形式:
一個很好的特點是,在我們的例子中,toys, computers和clothing都是在相同的上下文中(解釋器),它們可以來自完全不同的模塊或包。這是因為ChainMap通過引用存儲底層字典。
第一個例子是使用ChainMap一次搜索多個字典。
事實上,當構建ChainMap時,我們所做的就是有效地構建一系列字典。當查找清單中的一個項時,toys首先被查找,然后是computers,最后是clothing。
ChainMap真的只是一個映射鏈!
實際上,ChainMap的另一個任務是維護鏈的默認值。
我們將以一個命令行應用程序的例子來說明這是什么意思。
5、應用案例-CLI配置
讓我們面對現實,管理命令行應用程序的配置可能是困難的。配置來自多個源:命令行參數、環境變量、本地文件等。
我們通常實施優先級的概念:如果A和B都定義參數P,A的P值將被使用,因為它的優先級高于B。
例如,如果傳遞了命令行參數,我們可能希望在環境變量上使用命令行參數。如何輕松地管理配置源的優先級?
一個答案是將所有配置源存儲在ChainMap中。
因為ChainMap中的查找是按順序連續地對每個底層映射執行的(按照他們傳給構造函數的順序),所以我們可以很容易地實現我們尋找的優先級。
下面是一個簡單的命令行應用程序。調試參數從命令行參數、環境變量或硬編碼默認值中提取:
在執行腳本時,我們可以檢查是否首先在命令行參數中查找debug,然后是環境變量,最后是默認值:
這樣看上去就非常整潔,對吧?
6、我為什么關心?
坦率地說,ChainMap是那些你可以忽略的Python特性之一。
還有其他ChainMap的替代方案。例如,使用更新循環—例如創建一個dict并用字典update()它—可能奏效。但是,這只有在您不需要跟蹤項目的起源時才有效,就像我們的多源CLI配置示例中的情況一樣。但是,當你知道ChainMap存在的時候,ChainMap可以讓你更輕松,你的代碼更優雅。
7、總結
總而言之,我們一起看了ChainMap是什么,一些具體的使用示例,以及如何在現實生活中,性能關鍵的應用程序中使用ChainMap。如果您想了解更多關于Python的高性能數據容器的信息,請務必從Python的標準庫中collections模塊中查看其他出色類和函數。
?
八、UserDict
UserDict類是用作字典對象的外包裝。對這個類的需求已部分由直接創建dict的子類的功能所替代;不過這個類處理起來更容易,因為底層的字典可以作為屬性來訪問。
模擬一個字典類。這個實例的內容保存為一個正常字典,可以通過UserDict實例的data屬性存取。如果提供了initialdata 值, data 就被初始化為它的內容,注意一個 initialdata 的引用不會被保留作為其他用途。
UserDict 實例提供了以下屬性作為擴展方法和操作的支持:data一個真實的字典,用于保存 UserDict 類的內容。
九、UserList
這個類封裝了列表對象。它是一個有用的基礎類,對于你想自定義的類似列表的類,可以繼承和覆蓋現有的方法,也可以添加新的方法。這樣我們可以對列表添加新的行為。
對這個類的需求已部分由直接創建 list 的子類的功能所替代;不過,這個類處理起來更容易,因為底層的列表可以作為屬性來訪問。
模擬一個列表。這個實例的內容被保存為一個正常列表,通過 UserList 的 data 屬性存取。實例內容被初始化為一個 list 的copy,默認為?[]?空列表。list可以是迭代對象,比如一個Python列表,或者一個UserList 對象。
UserList?提供了以下屬性作為可變序列的方法和操作的擴展:data
一個 list 對象用于存儲 UserList 的內容。
子類化的要求: UserList 的子類需要提供一個構造器,可以無參數調用,或者一個參數調用。返回一個新序列的列表操作需要創建一個實現類的實例。它假定了構造器可以以一個參數進行調用,這個參數是一個序列對象,作為數據源。
如果一個分離的類不希望依照這個需求,所有的特殊方法就必須重寫;請參照源代碼進行修改。
?
十、UserString
UserString類是用作字符串對象的外包裝。對這個類的需求已部分由直接創建str的子類的功能所替代,不過這個類處理起來更容易,因為底層的字符串可以作為屬性來訪問。
模擬一個字符串對象。這個實例對象的內容保存為一個正常字符串,通過UserString的data屬性存取。實例內容初始化設置為seq的copy。seq 參數可以是任何可通過內建str()函數轉換為字符串的對象。
UserString 提供了以下屬性作為字符串方法和操作的額外支持:data一個真正的str對象用來存放 UserString 類的內容。
?
往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習及深度學習筆記等資料打印機器學習在線手冊深度學習筆記專輯《統計學習方法》的代碼復現專輯 AI基礎下載機器學習的數學基礎專輯 本站知識星球“黃博的機器學習圈子”(92416895) 本站qq群704220115。 加入微信群請掃碼:總結
以上是生活随笔為你收集整理的【万字长文详解】Python库collections,让你击败99%的Pythoner的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【学术相关】研究生第一篇学术论文常犯问题
- 下一篇: 谷歌浏览器书签栏怎么隐藏 谷歌Chrom