Python collections 模組筆記

wcode發表於2018-04-01

Python collections 模組筆記

namedtuple

collections.namedtuple 是一個工廠函式,它可以用來構建一個帶欄位名的元組和一個有名字的類——這個帶名字的類對除錯程式有很大幫助。

我們可以這樣建立一個 User 類:

 Card = collections.namedtuple('User', ['name', 'age', 'height'])
複製程式碼

如何用具名元組來記錄一個城市的資訊

In [1]: from collections import namedtuple

In [2]: City = namedtuple('City', 'name country population coordinates')

In [3]: tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))

In [4]: tokyo
Out[4]: City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667))

In [5]: tokyo.population
Out[5]: 36.933

In [6]: tokyo.coordinates
Out[6]: (35.689722, 139.691667)

In [7]: tokyo[1]
Out[7]: 'JP'
複製程式碼

建立一個具名元組需要兩個引數,一個是類名,另一個是類的各個欄位的名字。後者可以是由數個字串組成的可迭代物件,或者是由空格分隔開的欄位名組成的字串。

除了從普通元組那裡繼承來的屬性之外,具名元組還有一些自己專有的屬性。

In [8]: City._fields
Out[8]: ('name', 'country', 'population', 'coordinates')

In [9]: LatLong = namedtuple('LatLong', 'lat long')

In [10]: delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.613889, 77.208889))

In [11]: delhi = City._make(delhi_data)

In [12]: delhi._asdict()
Out[12]: 
OrderedDict([('name', 'Delhi NCR'),
             ('country', 'IN'),
             ('population', 21.935),
             ('coordinates', LatLong(lat=28.613889, long=77.208889))])

In [13]: for key, value in delhi._asdict().items():
    ...:     print(key + ':', value)
    ...:     
name: Delhi NCR
country: IN
population: 21.935
coordinates: LatLong(lat=28.613889, long=77.208889)

複製程式碼

_fields 屬性是一個包含這個類所有欄位名稱的元組。

_make() 通過接受一個可迭代物件來生成這個類的一個例項,它的作用跟 City(*delhi_data) 是一樣的。

_asdict() 把具名元組以 collections.OrderedDict 的形式返回,我們可以利用它來把元組裡的資訊友好地呈現出來。

defaultdict

首先我們看一個例子。

用 dict 統計一個 list 中字串出現的次數:

In [1]: langs = ['java', 'php', 'python', 'C#', 'kotlin', 'swift', 'python']

In [2]: res_dict = {}

In [3]: for lang in langs:
   ...:     if lang in res_dict:
   ...:         res_dict[lang] += 1
   ...:     else:
   ...:         res_dict[lang] = 1
   ...: 

In [4]: res_dict
Out[4]: {'C#': 1, 'java': 1, 'kotlin': 1, 'php': 1, 'python': 2, 'swift': 1}

複製程式碼

這裡每次迴圈都要判斷一次,可以呼叫 setdefault 方法來消除判斷。

In [1]: langs = ['java', 'php', 'python', 'C#', 'kotlin', 'swift', 'python']

In [2]: res_dict = {}

In [3]: for lang in langs:
   ...:     res_dict.setdefault(lang, 0)
   ...:     res_dict[lang] += 1
   ...: 

In [4]: res_dict
Out[4]: {'C#': 1, 'java': 1, 'kotlin': 1, 'php': 1, 'python': 2, 'swift': 1}
複製程式碼

但是現在還有一個錯誤,每次取值使還要進行一次判斷,否則如果值不存在就會拋異常:

In [5]: res_dict['c++']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-5-269671e9ed5a> in <module>()
----> 1 res_dict['c++']

KeyError: 'c++'

複製程式碼

有時候為了方便起見,就算某個鍵在對映裡不存在,我們也希望在通過這個鍵讀取值的時候能得到一個預設值。有兩個途徑能幫我們達到這個目的,一個是通過 defaultdict 這個型別而不是普通的 dict,另一個是給自己定義一個 dict 的子類,然後在子類中實現 __missing__ 方法。

使用 defaultdict

In [7]: from collections import defaultdict

In [8]: res_dict= defaultdict(int)

In [9]: for lang in langs:
   ...:     res_dict[lang] += 1
   ...: 

In [10]: res_dict
Out[10]: 
defaultdict(int,
            {'C#': 1,
             'java': 1,
             'kotlin': 1,
             'php': 1,
             'python': 2,
             'swift': 1})

In [11]: res_dict['c++']
Out[11]: 0
複製程式碼

這樣就完美解決了上述所有問題, defaultdict 建構函式接收一個可呼叫的物件,當 __getitem__ 方法找不到值的時候就會呼叫該物件返回一個值。

所以我們可以返回更復雜的預設值:

In [25]: def gen_dict():
    ...:     return {'name': 'None', 'age': 0}
    ...: 

In [26]: res_dict = defaultdict(gen_dict)

In [27]: res_dict['zhangsan']
Out[27]: {'age': 0, 'name': 'None'}

複製程式碼

__missing__ 方法

In [28]: class CustomDict(dict):
    ...:     
    ...:     def __missing__(self, key):
    ...:         return {'name': 'None', 'age': 18}
    ...: 

In [29]: res_dict = CustomDict()

In [30]: res_dict['lisi']
Out[30]: {'age': 18, 'name': 'None'}

複製程式碼

deque

collections.deque 類(雙向佇列)是一個執行緒安全、可以快速從兩端新增或者刪除元素的資料型別。而且如果想要有一種資料型別來存放“最近用到的幾個元素”,deque 也是一個很好的選擇。這是因為在新建一個雙向佇列的時候,你可以指定這個佇列的大小,如果這個佇列滿員了,還可以從反向端刪除過期的元素,然後在尾端新增新的元素。

In [1]: from collections import deque

In [2]: dq = deque(range(10), maxlen=10)

In [3]: dq
Out[3]: deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [4]: dq.rotate(3)

In [5]: dq
Out[5]: deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6])

In [6]: dq.rotate(-4)

In [7]: dq
Out[7]: deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])

In [8]: dq.appendleft(-1)

In [9]: dq
Out[9]: deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [10]: dq.extend([11, 22, 33])

In [11]: dq
Out[11]: deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33])

In [12]: dq.extendleft([10, 20, 30, 40])

In [13]: dq
Out[13]: deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8])

複製程式碼

maxlen 是一個可選引數,代表這個佇列可以容納的元素的數量,而且一旦設定,這個屬性就不能修改了。

佇列的旋轉操作 (rotate) 接受一個引數 n,當 n > 0 時,佇列的最右邊的 n 個元素會被移動到佇列的左邊。當 n < 0 時,最左邊的 n 個元素會被移動到右邊。

當試圖對一個已滿(len(d) == d.maxlen)的佇列做尾部新增操作的時候,它頭部的元素會被刪除掉。

extendleft(iter) 方法會把迭代器裡的元素逐個新增到雙向佇列的左邊,因此迭代器裡的元素會逆序出現在佇列裡。

Counter

這個對映型別會給鍵準備一個整數計數器。每次更新一個鍵的時候都會增加這個計數器。所以這個型別可以用來給可雜湊表物件計數,或者是當成多重集來用——多重集合就是集合裡的元素可以出現不止一次。Counter 實現了 + 和 - 運算子用來合併記錄,還有像 most_common([n]) 這類很有用的方法。most_common([n]) 會按照次序返回對映裡最常見的 n 個鍵和它們的計數

In [1]: from collections import Counter

In [2]: langs = ['java', 'php', 'python', 'C#', 'kotlin', 'swift', 'python']

In [3]: ct = Counter(langs)

In [4]: ct
Out[4]: Counter({'C#': 1, 'java': 1, 'kotlin': 1, 'php': 1, 'python': 2, 'swift': 1})

In [5]: ct.update(['java', 'c'])

In [6]: ct
Out[6]: 
Counter({'C#': 1,
         'c': 1,
         'java': 2,
         'kotlin': 1,
         'php': 1,
         'python': 2,
         'swift': 1})

In [7]: ct.most_common(2)
Out[7]: [('java', 2), ('python', 2)]
複製程式碼

當然,也可以直接操作字串:

In [9]: ct = Counter('abracadabra')

In [10]: ct
Out[10]: Counter({'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2})

In [11]: ct.update('aaaaazzz')

In [12]: ct
Out[12]: Counter({'a': 10, 'b': 2, 'c': 1, 'd': 1, 'r': 2, 'z': 3})

In [13]: ct.most_common(2)
Out[13]: [('a', 10), ('z', 3)]

複製程式碼

OrderedDict

這個型別在新增鍵的時候會保持順序,因此鍵的迭代次序總是一致的。OrderedDict 的 popitem 方法預設刪除並返回的是字典裡的最後一個元素,但是如果像 my_odict.popitem(last=False) 這樣呼叫它,那麼它刪除並返回第一個被新增進去的元素。

move_to_end(key, last=True) 將現有 key 移至有序字典的末尾。如果 last=True(預設),則 item 移動到右側,如果 last=False,則移動到開始。如果 key 不存在,則引發 KeyError

In [1]: from collections import OrderedDict

In [2]: d = OrderedDict.fromkeys('abcde')

In [3]: d.move_to_end('b')

In [4]: ''.join(d.keys())
Out[4]: 'acdeb'

In [5]: d.move_to_end('b', last=False)

In [6]: ''.join(d.keys())
Out[6]: 'bacde'

複製程式碼

由於 OrderedDict 會記住它的插入順序,因此它可以與 sorted 結合使用來建立一個排序後的字典:

In [11]: d = {'banana': 3, 'apple': 4, 'pear': 1, 'orange': 2}
# 根據 key 排序
In [12]: OrderedDict(sorted(d.items(), key=lambda t:t[0]))
Out[12]: OrderedDict([('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)])
# 根據 value 排序
In [13]: OrderedDict(sorted(d.items(), key=lambda t:t[1]))
Out[13]: OrderedDict([('pear', 1), ('orange', 2), ('banana', 3), ('apple', 4)])
# 根據 key 的長度排序
In [14]: OrderedDict(sorted(d.items(), key=lambda t: len(t[0])))
Out[14]: OrderedDict([('pear', 1), ('apple', 4), ('banana', 3), ('orange', 2)])

複製程式碼

刪除條目時,新排序的字典會保持排序順序。但是,當新增新的 key 時,key 被追加到最後,並不保持排序。

ChainMap

ChainMap 類提供用於快速連結多個 dict,以便將它們視為單個單元。它通常比建立新 dict 和執行多個 update() 呼叫要快得多。

In [1]: from collections import ChainMap

In [2]: d1 = {'java': 3, 'python': 4}

In [3]: d2 = {'c++': 1, 'java': 2}

In [4]: for key, val in ChainMap(d1, d2).items():
   ...:     print(key, val)
   ...:     
c++ 1
java 3
python 4

複製程式碼

後出現的重複的 key 將被忽略

ChainMap 將連結的 dict 儲存在一個列表中。該列表是公開的,可以使用 maps 屬性進行訪問或更新。

In [10]: c1 = ChainMap(d1, d2)

In [11]: c1.maps[0]
Out[11]: {'java': 3, 'python': 4}

In [12]: c1.maps[0]['python'] = 2

In [13]: c1.items()
Out[13]: ItemsView(ChainMap({'java': 3, 'python': 2}, {'c++': 1, 'java': 2}))

In [14]: dict(c1)
Out[14]: {'c++': 1, 'java': 3, 'python': 2}

複製程式碼

參考

python必學模組-collections
8.3. collections — Container datatypes
《流暢的 Python》相關章節

相關文章