1.模塊簡介
collections包含了一些特殊的容器,針對Python內置的容器,例如list、dict、set和tuple,提供了另一種選擇;
namedtuple,可以創建包含名稱的tuple;
deque,類似於list的容器,可以快速的在隊列頭部和尾部添加、刪除元素;
Counter,dict的子類,計算可hash的對象;
OrderedDict,dict的子類,可以記住元素的添加順序;
defaultdict,dict的子類,可以調用提供默認值的函數;
2.模塊使用
2.1 Counter
counter可以支持方便、快速的計數,例如,
from collections import * cnt = Counter() wordList = ["a","b","c","c","a","a"] for word in wordList: cnt[word] += 1 print cnt
控制台輸出,
Counter({'a': 3, 'c': 2, 'b': 1})
對可迭代的對象進行計數或者從另一個映射(counter)進行初始化,
>>> c = Counter()#一個新的,空的counter >>> c Counter() >>> c = Counter("gallahad")#從可迭代的字符串初始化counter >>> c Counter({'a': 3, 'l': 2, 'h': 1, 'g': 1, 'd': 1}) >>> c = Counter({'red':4,'blue':2})#從映射初始化counter >>> c Counter({'red': 4, 'blue': 2}) >>> c = Counter(cats = 4,dogs = 8)#從args初始化counter >>> c Counter({'dogs': 8, 'cats': 4})
Counter對象類似於字典,如果某個項缺失,會返回0,而不是報出KeyError;
>>> c = Counter(['eggs','ham']) >>> c['bacon']#沒有'bacon' 0 >>> c['eggs']#有'eggs' 1
將一個元素的數目設置為0,並不能將它從counter中刪除,使用del可以將這個元素刪除;
>>> c Counter({'eggs': 1, 'ham': 1}) >>> c['eggs'] = 0 >>> c Counter({'ham': 1, 'eggs': 0})#'eggs'依然存在 >>> del c['eggs'] >>> c Counter({'ham': 1})#'eggs'不存在
Counter對象支持以下三個字典不支持的方法,elements(),most_common(),subtract();
element(),返回一個迭代器,每個元素重復的次數為它的數目,順序是任意的順序,如果一個元素的數目少於1,那么elements()就會忽略它;
>>> c = Counter(a=2,b=4,c=0,d=-2,e = 1) >>> c Counter({'b': 4, 'a': 2, 'e': 1, 'c': 0, 'd': -2}) >>> list(c.elements()) ['a', 'a', 'b', 'b', 'b', 'b', 'e']
most_common(),返回一個列表,包含counter中n個最大數目的元素
,如果忽略n或者為None,most_common()將會返回counter中的所有元素,元素有着相同數目的將會以任意順序排列;
>>> Counter('abracadabra').most_common(3) [('a', 5), ('r', 2), ('b', 2)] >>> Counter('abracadabra').most_common() [('a', 5), ('r', 2), ('b', 2), ('c', 1), ('d', 1)] >>> Counter('abracadabra').most_common(None) [('a', 5), ('r', 2), ('b', 2), ('c', 1), ('d', 1)]
subtract(),從一個可迭代對象中或者另一個映射(或counter)中,元素相減,類似於dict.update(),但是subtracts 數目而不是替換它們,輸入和輸出都有可能為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, 'c': 3, 'b': 0, 'd': -6})
update(),從一個可迭代對象中或者另一個映射(或counter)中所有元素相加,類似於dict.upodate,是數目相加而非替換它們,另外,可迭代對象是一個元素序列,而非(key,value)對構成的序列;
>>> c Counter({'a': 4, 'b': 2, 'c': 0, 'd': -2}) >>> d Counter({'d': 4, 'b': 2, 'a': 1, 'c': -3}) >>> c.update(d) >>> c Counter({'a': 5, 'b': 4, 'd': 2, 'c': -3})
Counter對象常見的操作,
>>> c Counter({'a': 5, 'b': 4, 'd': 2, 'c': -3}) >>> sum(c.values())# 統計所有的數目 8 >>> list(c)# 列出所有唯一的元素 ['a', 'c', 'b', 'd'] >>> set(c)# 轉換為set set(['a', 'c', 'b', 'd']) >>> dict(c)# 轉換為常規的dict {'a': 5, 'c': -3, 'b': 4, 'd': 2} >>> c.items()# 轉換為(elem,cnt)對構成的列表 [('a', 5), ('c', -3), ('b', 4), ('d', 2)] >>> c.most_common()[:-4:-1]# 輸出n個數目最小元素 [('c', -3), ('d', 2), ('b', 4)] >>> c += Counter()# 刪除數目為0和為負的元素 >>> c Counter({'a': 5, 'b': 4, 'd': 2}) >>> Counter(dict(c.items()))# 從(elem,cnt)對構成的列表轉換為counter Counter({'a': 5, 'b': 4, 'd': 2}) >>> c.clear()# 清空counter >>> c Counter()
在Counter對象進行數學操作,得多集合(counter中元素數目大於0)加法和減法操作,是相加或者相減對應元素的數目;交集和並集返回對應數目的最小值和最大值;每個操作均接受暑促是有符號的數目,但是輸出並不包含數目為0或者為負的元素;
>>> c = Counter(a=3,b=1,c=-2) >>> d = Counter(a=1,b=2,c=4) >>> c+d#求和 Counter({'a': 4, 'b': 3, 'c': 2}) >>> c-d#求差 Counter({'a': 2}) >>> c & d#求交集 Counter({'a': 1, 'b': 1}) >>> c | d#求並集 Counter({'c': 4, 'a': 3, 'b': 2})
2.2 deque
deque是棧和隊列的一種廣義實現,deque是"double-end queue"的簡稱;deque支持線程安全、有效內存地以近似O(1)的性能在deque的兩端插入和刪除元素,盡管list也支持相似的操作,但是它主要在固定長度操作上的優化,從而在pop(0)和insert(0,v)(會改變數據的位置和大小)上有O(n)的時間復雜度。
deque支持如下方法,
append(x), 將x添加到deque的右側;
appendleft(x), 將x添加到deque的左側;
clear(), 將deque中的元素全部刪除,最后長度為0;
count(x), 返回deque中元素等於x的個數;
extend(iterable), 將可迭代變量iterable中的元素添加至deque的右側;
extendleft(iterable), 將變量iterable中的元素添加至deque的左側,往左側添加序列的順序與可迭代變量iterable中的元素相反;
pop(), 移除和返回deque中最右側的元素,如果沒有元素,將會報出IndexError;
popleft(), 移除和返回deque中最左側的元素,如果沒有元素,將會報出IndexError;
remove(value), 移除第一次出現的value,如果沒有找到,報出ValueError;
reverse(), 反轉deque中的元素,並返回None;
rotate(n), 從右側反轉n步,如果n為負數,則從左側反轉,d.rotate(1)等於d.appendleft(d.pop());
maxlen, 只讀的屬性,deque的最大長度,如果無解,就返回None;
除了以上的方法之外,deque還支持迭代、序列化、len(d)、reversed(d)、copy.copy(d)、copy.deepcopy(d),通過in操作符進行成員測試和下標索引,索引的時間復雜度是在兩端是O(1),在中間是O(n),為了快速獲取,可以使用list代替。
>>> from collections import deque >>> d = deque('ghi')# 新建一個deque,有三個元素 >>> for ele in d:# 遍歷deque ... print ele.upper() ... ... G H I >>> d.append('j')# deque右側添加一個元素 >>> d.appendleft('f')# deque左側添加一個元素 >>> d# 打印deque deque(['f', 'g', 'h', 'i', 'j']) >>> d.pop()# 返回和移除最右側元素 'j' >>> d.popleft()# 返回和移除最左側元素 'f' >>> list(d)# 以列表形式展示出deque的內容 ['g', 'h', 'i'] >>> d[0]# 獲取最左側的元素 'g' >>> d[-1]# 獲取最右側的元素 'i' >>> list(reversed(d))# 以列表形式展示出倒序的deque的內容 ['i', 'h', 'g'] >>> 'h' in d# 在deque中搜索 True >>> d.extend('jkl')# 一次添加多個元素 >>> d deque(['g', 'h', 'i', 'j', 'k', 'l']) >>> d.rotate(1)# 往右側翻轉 >>> d deque(['l', 'g', 'h', 'i', 'j', 'k']) >>> d.rotate(-1)# 往左側翻轉 >>> d deque(['g', 'h', 'i', 'j', 'k', 'l']) >>> deque(reversed(d))# 以逆序新建一個deque deque(['l', 'k', 'j', 'i', 'h', 'g']) >>> d.clear()# 清空deque >>> d.pop()# 不能在空的deque上pop Traceback (most recent call last): File "<input>", line 1, in <module> IndexError: pop from an empty deque >>> d.extendleft('abc')# 以輸入的逆序向左擴展 >>> d deque(['c', 'b', 'a'])
其他的應用:
1.限定長度的deque提供了Unix中tail命令相似的功能;
from collections import deque def tail(filename,n = 10): "Return the last n lines of a file" return deque(open(filename),n) print tail("temp.txt",10)
2.使用deque維護一個序列(右側添加元素,左側刪除元素)中窗口的平均值;
from collections import deque import itertools def moving_average(iterable,n = 3): it = iter(iterable) d = deque(itertools.islice(it,n-1)) # 第一次只有兩個元素,再右移的過程中,需要先刪除最左端的元素,因此現在最左端加入0 d.appendleft(0) s = sum(d) for ele in it: # 刪除最左端的元素,再加上新元素 s += ele - d.popleft() # 右端添加新元素 d.append(ele) yield s / float(n) array = [40,30,50,46,39,44] for ele in moving_average(array,n=3): print ele
3.rotate()方法提供了一種實現deque切片和刪除的方式,例如,del d[n]依賴於rotate方法的純Python實現,如下,
from collections import deque def delete_nth(d,n): # 將前n個元素翻轉到右側 d.rotate(-n) # 刪除第n個元素 d.popleft() # 再將后n個元素翻轉到左側 d.rotate(n) d = deque("abcdefg") delete_nth(d,n = 3) print d
4.slice依賴於rotate方法的純Python實現,如下,
from collections import deque def slice(d,m,n): # 先將前面m個元素翻轉到右側 d.rotate(-m) i = m sliceList = [] # 依次將[m,n]區間內的元素出棧 while i < n: item = d.popleft() sliceList.append(item) i+=1 # 再將出棧的元素擴展到deque右側 d.extend(sliceList) # 再將后面n個元素翻轉到左側 d.rotate(n) return sliceList d = deque("abcdefg") print slice(d,1,5)
2.3 defaultdict
defaultdict是內置數據類型dict的一個子類,基本功能與dict一樣,只是重寫了一個方法__missing__(key)和增加了一個可寫的對象變量default_factory。
>>> dir(defaultdict) ['__class__', '__cmp__', '__contains__', '__copy__', '__delattr__', '__delitem__ ', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__ ', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__missing__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', ' __setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear ', 'copy', 'default_factory', 'fromkeys', 'get', 'has_key', 'items', 'iteritems' , 'iterkeys', 'itervalues', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'v alues', 'viewitems', 'viewkeys', 'viewvalues'] >>> dir(dict) ['__class__', '__cmp__', '__contains__', '__delattr__', '__delitem__', '__doc__' , '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '_ _new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__' , '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get ', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'pop', 'po pitem', 'setdefault', 'update', 'values', 'viewitems', 'viewkeys', 'viewvalues']
missing(key)
- 如果default_factory屬性為None,就報出以key作為遍歷的KeyError異常;
- 如果default_factory不為None,就會向給定的key提供一個默認值,這個值插入到詞典中,並返回;
- 如果調用default_factory報出異常,這個異常在傳播時不會改變;
- 這個方法是當要求的key不存在時,dict類中的__getitem()__方法所調用,無論它返回或者報出什么,最終返回或報出給__getitem()__;
- 只有__getitem__()才能調用__missing__(),這意味着,如果get()起作用,如普通的詞典,將會返回None作為默認值,而不是使用default_factory;
default_factory, 這個屬性用於__missing__()方法,使用構造器中的第一個參數初始化;
使用list作為default_factory,很容易將一個key-value的序列轉換為一個關於list的詞典;
>>> from collections import * >>> s = [('yellow',1),('blue',2),('yellow',3),('blue',4),('red',5)] >>> d = defaultdict(list) >>> for k,v in s: d[k].append(v) ... >>> d.items() [('blue', [2, 4]), ('red', [5]), ('yellow', [1, 3])]
當每一個key第一次遇到時,還沒有准備映射,首先會使用default_factory函數自動創建一個空的list,list.append()操作將value添加至新的list中,當key再次遇到時,通過查表,返回對應這個key的list,list.append()會將新的value添加至list,這個技術要比dict.setdefault()要簡單和快速。
>>> e = {} >>> for k,v in s: e.setdefault(k,[]).append(v) ... >>> e.items() [('blue', [2, 4]), ('red', [5]), ('yellow', [1, 3])]
設置default_factory為int,使得defaultdict可以用於計數,
>>> s = "mississippi" >>> d = defaultdict(int) >>> for k in s: d[k]+=1 ... >>> d.items() [('i', 4), ('p', 2), ('s', 4), ('m', 1)]
當一個字母第一次遇到,默認從default_factory中調用int()用於提供一個默認為0的計數,遞增操作會增加每個字母的計數。
函數int()經常返回0,是常量函數的一種特例。一種更快和更靈活的創建常量函數的方式是使用itertools.repeat(),可以提供任意常量值(不僅僅是0),
>>> import itertools >>> def constant_factory(value): ... return itertools.repeat(value).next ... >>> d = defaultdict(constant_factory('<missing>')) >>> d.update(name = "John",action = "ran") >>> "%(name)s %(action)s to %(object)s"%d 'John ran to <missing>'
將default_factory設置為set,使得defaultdict可以建立一個關於set的詞典,
>>> s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)] >>> d = defaultdict(set) >>> for k,v in s:d[k].add(v) ... >>> d.items() [('blue', set([2, 4])), ('red', set([1, 3]))]
2.4 namedtuple
命名的元組,意味給元組中的每個位置賦予含義,意味着代碼可讀性更強,namedtuple可以在任何常規元素使用的地方使用,而且它可以通過名稱來獲取字段信息而不僅僅是通過位置索引。
>>> from collections import * >>> Point = namedtuple('Point',['x','y'],verbose = True) class Point(tuple): 'Point(x, y)' __slots__ = () _fields = ('x', 'y') def __new__(_cls, x, y): 'Create new instance of Point(x, y)' return _tuple.__new__(_cls, (x, y)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new Point object from a sequence or iterable' result = new(cls, iterable) if len(result) != 2: raise TypeError('Expected 2 arguments, got %d' % len(result)) return result def __repr__(self): 'Return a nicely formatted representation string' return 'Point(x=%r, y=%r)' % self def _asdict(self): 'Return a new OrderedDict which maps field names to their values' return OrderedDict(zip(self._fields, self)) def _replace(_self, **kwds): 'Return a new Point object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('x', 'y'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return tuple(self) __dict__ = _property(_asdict) def __getstate__(self): 'Exclude the OrderedDict from pickling' pass x = _property(_itemgetter(0), doc='Alias for field number 0') y = _property(_itemgetter(1), doc='Alias for field number 1')
>>> p = Point(11,y = 22)# 實例化一個對象,可以使用位置或者關鍵字 >>> p[0] + p[1]# 通過索引訪問元組中的元素 33 >>> x,y = p# 分開,類似於常規的元組 >>> x,y (11, 22) >>> p.x + p.y# 通過名稱訪問元素 33 >>> p# 可讀的__repr__,通過name = value風格 Point(x=11, y=22)
namedtuple在給csv或者sqlite3返回的元組附上名稱特別有用,
from collections import * import csv EmployeeRecord = namedtuple('EmployeeRecord','name, age, title, department, paygrade') for emp in map(EmployeeRecord._make,csv.reader(open("employee.csv","rb"))): print emp.name,emp.title # import sqlite3 # conn = sqlite3.connect('/companydata') # cursor = conn.cursor() # cursor.execute('SELECT name, age, title, department, paygrade FROM employees') # for emp in map(EmployeeRecord._make, cursor.fetchall()): # print emp.name, emp.title
控制台輸出,
Jim RD Tom Manager
除了從tuples繼承的方法之外,namedtuple還支持三種方法和一個屬性,為了避免和名稱沖突,這些方法和屬性以下划線開始。
**somenamedtuple._make(),** 從已有的序列或者可迭代的對象中創建一個新的對象;
>>> Point = namedtuple('Point', ['x', 'y']) >>> t = [33,44] >>> Point._make(t) Point(x=33, y=44)
**somenamedtuple._asdict(),** 返回一個OrderDict,由名稱到對應值建立的映射;
>>> p = Point(x = 11,y = 22) >>> p Point(x=11, y=22) >>> pDict = p._asdict() >>> pDict OrderedDict([('x', 11), ('y', 22)])
**somenamedtuple._replace(),** 返回一個新的namedtuple對象,用新值替換指定名稱中的值;
>>> p2 = p._replace(x = 33) >>> p2 Point(x=33, y=22)
**somenamedtuple._fields,** 以字符串構成的元組的形式返回namedtuple中的名稱,在自省或者基於一個已經存在的namedtuple中創建新的namedtuple時,非常有用;
>>> p._fields ('x', 'y') >>> Color = namedtuple('Color',"red green blu") >>> Pixel = namedtuple('Pixel',Point._fields + Color._fields) >>> Pixel(11,22,128,255,0) Pixel(x=11, y=22, red=128, green=255, blu=0)
當名稱存儲在字符串中,可以使用getattr()函數進行檢索,
>>> getattr(p,'x') 11
使用**操作符,可以將一個字典轉換成namedtuple,
>>> d = {'x':11,'y':22} >>> Point(**d) Point(x=11, y=22)
由於namedtuple也是Python中的一個類,因此再子類中,它很容易添加或者修改一些功能,如下是添加一個可計算名稱和固定長度的輸出格式;子類中的__slots__是一個空的元組,可以通過避免詞典實例的創建來節約內存開銷;
class Point(namedtuple('Point','x y')): __slots__ = () @property def hypot(self): return (self.x ** 2 + self.y**2) ** 0.5 def __str__(self): return "Point:x = %6.3f y = %6.3f hypot = %6.3f" %(self.x,self.y,self.hypot) for p in Point(3,4),Point(14,5/7.): print p
控制台輸出,
Point:x = 3.000 y = 4.000 hypot = 5.000 Point:x = 14.000 y = 0.714 hypot = 14.018
子類在增加、存儲名稱時,並不是非常有用,相反,可以容易地通過_fields屬性來創建一個新的namedtuple;
>>> Point3D = namedtuple("Point3D",Point._fields + ('z',)) >>> Point3D._fields ('x', 'y', 'z')
默認值可以通過_replace()來實現,以便於標准化一個原型實例;
>>> Account = namedtuple('Account','owner balance transaction_count') >>> default_account = Account('<owner name>',0.0,0) >>> johns_account = default_account._replace(owner = "John") >>> johns_account Account(owner='John', balance=0.0, transaction_count=0)
枚舉類型常量可以通過namedtuple來實現,更簡單和有效的方式是通過意見簡單的類聲明;
Status = namedtuple('Status','open pending closed')._make(range(3)) print Status class Status: open, pending, closed = range(3) print Status.open print Status.pending print Status.closed
控制台輸出,
Status(open=0, pending=1, closed=2) 0 1 2
2.5 OrderedDict
OrderedDict類似於正常的詞典,只是它記住了元素插入的順序,當在有序的詞典上迭代時,返回的元素就是它們第一次添加的順序。
class collections.OrderedDict,返回已給dict的子類,支持常規的dict的方法,OrderedDict是一個記住元素首次插入順序的詞典,如果一個元素重寫已經存在的元素,那么原始的插入位置保持不變,如果刪除一個元素再重新插入,那么它就在末尾。
OrderedDict.popitem(last=True),popitem方法返回和刪除一個(key,value)對,如果last=True,就以LIFO方式執行,否則以FIFO方式執行。
OrderedDict也支持反向迭代,例如reversed()。
OrderedDict對象之間的相等測試,例如,list(od1.items()) == list(od2.items()),是對順序敏感的;OrderedDict和其他的映射對象(例如常規的詞典)之間的相等測試是順序不敏感的,這就允許OrderedDict對象可以在使用常規詞典的地方替換掉常規詞典。
OrderedDict構造器和update()方法可以接受關鍵字變量,但是它們丟失了順序,因為Python的函數調用機制是將一個無序的詞典傳入關鍵字變量。
一個有序的詞典記住它的成員插入的順序,可以使用排序函數,將其變為排序的詞典,
>>> d = {"banana":3,"apple":2,"pear":1,"orange":4} >>> # dict sorted by key >>> OrderedDict(sorted(d.items(),key = lambda t:t[0])) OrderedDict([('apple', 2), ('banana', 3), ('orange', 4), ('pear', 1)]) >>> # dict sorted by value >>> OrderedDict(sorted(d.items(),key = lambda t:t[1])) OrderedDict([('pear', 1), ('apple', 2), ('banana', 3), ('orange', 4)]) >>> # dict sorted by length of key string >>>a = OrderedDict(sorted(d.items(),key = lambda t:len(t[0]))) >>>a OrderedDict([('pear', 1), ('apple', 2), ('orange', 4), ('banana', 3)]) >>> del a['apple'] >>> a OrderedDict([('pear', 1), ('orange', 4), ('banana', 3)]) >>> a["apple"] = 2 >>> a OrderedDict([('pear', 1), ('orange', 4), ('banana', 3), ('apple', 2)])
當元素刪除時,排好序的詞典保持着排序的順序;但是當新元素添加時,就會被添加到末尾,就不能保持已排序。
創建一個有序的詞典,可以記住最后插入的key的順序,如果一個新的元素要重寫已經存在的元素,那么原始的插入位置就會改變成末尾,
>>> class LastUpdatedOrderedDict(OrderedDict): ... def __setitem__(self,key,value): ... if key in self: ... del self[key] ... OrderedDict.__setitem__(self, key, value) ... >>> obj = LastUpdatedOrderedDict() >>> obj["apple"] = 2 >>> obj["windows"] = 3 >>> obj LastUpdatedOrderedDict([('apple', 2), ('windows', 3)]) >>> obj["apple"] = 1 >>> obj LastUpdatedOrderedDict([('windows', 3), ('apple', 1)])
一個有序的詞典可以和Counter類一起使用,counter對象就可以記住元素首次出現的順序;
class OrderedCounter(Counter,OrderedDict): def __repr__(self): return "%s(%r)"%(self.__class__.__name__,OrderedDict(self)) def __reduce__(self): return self.__class__,(OrderedDict(self)) #和OrderDict一起使用的Counter對象 obj = OrderedCounter() wordList = ["b","a","c","a","c","a"] for word in wordList: obj[word] += 1 print obj # 普通的Counter對象 cnt = Counter() wordList = ["b","a","c","a","c","a"] for word in wordList: cnt[word] += 1 print cnt
控制台輸出,
OrderedCounter(OrderedDict([('b', 1), ('a', 3), ('c', 2)])) Counter({'a': 3, 'c': 2, 'b': 1})