collections --- 容器資料型態

原始碼:Lib/collections/__init__.py


這個模組實作了一些特別的容器資料型態,用來替代 Python 一般內建的容器,例如dict(字典)、list(串列)、set(集合)和tuple(元組)。

namedtuple()

用來建立具名欄位的 tuple 子類別的工廠函式

deque

一個類似 list 的容器,可以快速的在頭尾加入 (append) 元素與移除 (pop) 元素

ChainMap

一個類似 dict 的類別,用來為多個對映 (mapping) 建立單一的視圖 (view)

Counter

dict 的子類別,用來計算可雜湊物件的數量

OrderedDict

dict 的子類別,會記錄物件被加入的順序

defaultdict

dict 的子類別,當值不存在 dict 中時會呼叫一個提供預設值的工廠函式

UserDict

dict 物件的包裝器 (wrapper),簡化了 dict 的子類別化過程

UserList

list 物件的包裝器,簡化了 list 的子類別化過程

UserString

字串物件的包裝器,簡化了字串的子類別化過程

ChainMap 物件

在 3.3 版被加入.

ChainMap(對映鏈結)類別的目的是快速將數個對映連結在一起,讓它們可以被當作一個單元來處理。它通常會比建立一個新的字典並多次呼叫update() 來得更快。

這個類別可用於模擬巢狀作用域 (nested scopes),且在模板化 (templating) 時能派上用場。

classcollections.ChainMap(*maps)

一個ChainMap 將多個 dict 或其他對映組合在一起,建立一個獨立、可更新的視圖。如果沒有指定maps,預設會提供一個空字典讓每個新鏈結都至少有一個對映。

底層的對映儲存於一個 list 中,這個 list 是公開的且可透過maps 屬性存取或更新,沒有其他狀態 (state)。

檢索 (lookup) 陸續查詢底層對映,直到鍵被找到,然而讀取、更新和刪除就只會對第一個對映操作。

ChainMap 透過參照將底層對映合併,所以當一個底層對映被更新,這些改變也會反映到ChainMap

所有常見的字典方法都有支援。此外,還有一個maps 屬性 (attribute)、一個建立子上下文 (subcontext) 的方法、和一個能夠存取除了第一個以外其他所有對映的特性 (property):

maps

一個可被更新的對映列表,這個列表是按照被搜尋的順序來排列,在 ChainMap 中它是唯一被儲存的狀態,可被修改來變換搜尋順序。回傳的列表都至少包含一個對映。

new_child(m=None,**kwargs)

回傳包含一個新對映的ChainMap, 新對映後面接著目前實例的所有現存對映。如果有給定mm 會成為那個最前面的新對映;若沒有指定,則會加上一個空 dict,如此一來呼叫d.new_child() 就等同於ChainMap({},*d.maps)。這個方法用於建立子上下文,而保持父對映的不變。

在 3.4 版的變更:加入可選參數m

在 3.10 版的變更:增加了對關鍵字引數的支援。

parents

回傳一個包含除了第一個以外所有其他對映的新ChainMap 的特性,可用於需要跳過第一個對映的搜尋。使用情境類似於在巢狀作用域當中使用nonlocal 關鍵字,也可與內建函式super() 做類比。引用d.parents 等同於ChainMap(*d.maps[1:])

注意,一個ChainMap 的疊代順序是透過由後往前掃描對映而定:

>>>baseline={'music':'bach','art':'rembrandt'}>>>adjustments={'art':'van gogh','opera':'carmen'}>>>list(ChainMap(adjustments,baseline))['music', 'art', 'opera']

這和呼叫dict.update() 結果的順序一樣是從最後一個對映開始:

>>>combined=baseline.copy()>>>combined.update(adjustments)>>>list(combined)['music', 'art', 'opera']

在 3.9 版的變更:支援||= 運算子,詳見PEP 584

也參考

ChainMap 範例和用法

此章節提供了多種操作 ChainMap 的案例。

模擬 Python 內部檢索鏈結的例子:

importbuiltinspylookup=ChainMap(locals(),globals(),vars(builtins))

讓使用者指定的命令列引數優先於環境變數、再優先於預設值的範例:

importos,argparsedefaults={'color':'red','user':'guest'}parser=argparse.ArgumentParser()parser.add_argument('-u','--user')parser.add_argument('-c','--color')namespace=parser.parse_args()command_line_args={k:vfork,vinvars(namespace).items()ifvisnotNone}combined=ChainMap(command_line_args,os.environ,defaults)print(combined['color'])print(combined['user'])

ChainMap 類別模擬巢狀上下文的範例模式:

c=ChainMap()# Create root contextd=c.new_child()# Create nested child contexte=c.new_child()# Child of c, independent from de.maps[0]# Current context dictionary -- like Python's locals()e.maps[-1]# Root context -- like Python's globals()e.parents# Enclosing context chain -- like Python's nonlocalsd['x']=1# Set value in current contextd['x']# Get first key in the chain of contextsdeld['x']# Delete from current contextlist(d)# All nested valueskind# Check all nested valueslen(d)# Number of nested valuesd.items()# All nested itemsdict(d)# Flatten into a regular dictionary

ChainMap 類別只對鏈結中第一個對映來做寫入或刪除,但檢索則會掃過整個鏈結。但如果需要對更深層的鍵寫入或刪除,透過定義一個子類別來實作也不困難:

classDeepChainMap(ChainMap):'Variant of ChainMap that allows direct updates to inner scopes'def__setitem__(self,key,value):formappinginself.maps:ifkeyinmapping:mapping[key]=valuereturnself.maps[0][key]=valuedef__delitem__(self,key):formappinginself.maps:ifkeyinmapping:delmapping[key]returnraiseKeyError(key)>>>d=DeepChainMap({'zebra':'black'},{'elephant':'blue'},{'lion':'yellow'})>>>d['lion']='orange'# update an existing key two levels down>>>d['snake']='red'# new keys get added to the topmost dict>>>deld['elephant']# remove an existing key one level down>>>d# display resultDeepChainMap({'zebra':'black','snake':'red'},{},{'lion':'orange'})

Counter 物件

提供一個支援方便且快速計數的計數器工具,例如:

>>># Tally occurrences of words in a list>>>cnt=Counter()>>>forwordin['red','blue','red','green','blue','blue']:...cnt[word]+=1...>>>cntCounter({'blue': 3, 'red': 2, 'green': 1})>>># Find the ten most common words in Hamlet>>>importre>>>words=re.findall(r'\w+',open('hamlet.txt').read().lower())>>>Counter(words).most_common(10)[('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631), ('you', 554),  ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)]
classcollections.Counter([iterable-or-mapping])

Counterdict 的子類別,用來計算可雜湊物件的數量。它是將物件與其計數作為字典的鍵值對儲存的集合容器。計數可以是包含 0 與負數的任何整數值。Counter 類別類似其他程式語言中的 bags 或 multisets。

被計數的元素來自一個iterable 或是被其他的mapping(或 Counter)初始化:

>>>c=Counter()# a new, empty counter>>>c=Counter('gallahad')# a new counter from an iterable>>>c=Counter({'red':4,'blue':2})# a new counter from a mapping>>>c=Counter(cats=4,dogs=8)# a new counter from keyword args

Counter 物件擁有一個字典的使用介面,除了遇到 Counter 中沒有的值時會回傳計數 0 取代KeyError 這點不同:

>>>c=Counter(['eggs','ham'])>>>c['bacon']# count of a missing element is zero0

將一個值的計數設為 0 並不會真的從 Counter 中刪除這個元素,要使用del 來將其刪除:

>>>c['sausage']=0# counter entry with a zero count>>>delc['sausage']# del actually removes the entry

在 3.1 版被加入.

在 3.7 版的變更:作為dict 的子類別,Counter 繼承了記憶插入順序的功能。對Counter 做數學運算後同樣保留順序性,其結果是依照各個元素在運算元左邊出現的時間先後、再按照運算元右邊出現的時間先後來排列。

除了字典原本就有的方法外,Counter 物件額外支援數個新方法:

elements()

回傳每個元素都重複出現計算次數的 iterator(疊代器)物件,其中元素的回傳順序是依照各元素首次出現的時間先後。如果元素的出現次數小於 1,elements() 方法會忽略這些元素。

>>>c=Counter(a=4,b=2,c=0,d=-2)>>>sorted(c.elements())['a', 'a', 'a', 'a', 'b', 'b']
most_common([n])

回傳一個 list,包含出現最多次的n 個元素及其出現次數,並按照出現次數排序。如果n 被省略或者為Nonemost_common() 會回傳所有 counter 中的元素。出現次數相同的元素會按照首次出現的時間先後來排列:

>>>Counter('abracadabra').most_common(3)[('a', 5), ('b', 2), ('r', 2)]
subtract([iterable-or-mapping])

減去自一個iterable 或另一個對映(或 Counter)中的計數元素,行為類似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)>>>cCounter({'a': 3, 'b': 0, 'c': -3, 'd': -6})

在 3.2 版被加入.

total()

計算總計數值。

>>>c=Counter(a=10,b=5,c=0)>>>c.total()15

在 3.10 版被加入.

通常來說字典方法也可以用於Counter 物件,除了以下兩個作用方式與計數器不同。

fromkeys(iterable)

此類別方法沒有被實作於Counter 物件中。

update([iterable-or-mapping])

加上自一個iterable 計算出的計數或加上另一個mapping(或 Counter)中的計數,行為類似dict.update() 但是是為了加上計數而非取代其值。另外,iterable 需要是一串將被計算個數元素的序列,而非元素為(key,value) 形式的序列。

Counter 支援相等性、子集和超集關係的 rich comparison 運算子:==!=<<=>>=。這些檢測會將不存在的元素之計數值當作零,因此Counter(a=1)==Counter(a=1,b=0) 將回傳真值。

在 3.10 版的變更:增加了 rich comparison 運算。

在 3.10 版的變更:在相等性運算中,不存在的元素之計數值會被當作零。在此之前,Counter(a=3)Counter(a=3,b=0) 被視為不同。

使用Counter 物件的常見使用模式:

c.total()# total of all countsc.clear()# reset all countslist(c)# list unique elementsset(c)# convert to a setdict(c)# convert to a regular dictionaryc.items()# access the (elem, cnt) pairsCounter(dict(list_of_pairs))# convert from a list of (elem, cnt) pairsc.most_common()[:-n-1:-1]# n least common elements+c# remove zero and negative counts

為結合多個Counter 物件以產生 multiset(多重集合,擁有大於 0 計數元素的計數器),有提供了幾種數學操作。加法和減法是根據各個對應元素分別將 Counter 加上和減去計數,交集和聯集分別回傳各個元素最小和最大計數,相等性與包含性運算則會比較對應的計數。每一個操作都可以接受輸入帶有正負號的計數,但輸出的 Counter 則會將擁有小於或等於 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# intersection:  min(c[x], d[x])Counter({'a': 1, 'b': 1})>>>c|d# union:  max(c[x], d[x])Counter({'a': 3, 'b': 2})>>>c==d# equality:  c[x] == d[x]False>>>c<=d# inclusion:  c[x] <= d[x]False

加減法的一元運算子分別是加上空的 Counter 和從空 Counter 減去的簡寫。

>>>c=Counter(a=2,b=-4)>>>+cCounter({'a': 2})>>>-cCounter({'b': 4})

在 3.3 版被加入:開始支援加減一元運算子和 multiset 的原地 (in-place) 操作。

備註

Counter 主要是被設計來操作正整數以當作使用中的計數,但為了某些會用到計數之值為負數或為其他型別的案例中,Counter 也小心地被設計成不會預先排除這些特殊元素。為了輔助使用於上述案例,這一小節記錄了最小範圍和型別限制。

  • Counter 類別本身是字典的子類別,且不限制其鍵與值。值被用來表示計數,但實際上你可以儲存任何值。

  • 使用most_common() 方法的唯一條件是其值要是可被排序的。

  • 像是c[key]+=1 的原地操作中,其值之型別只必須支援加減,所以分數、浮點數、十進位數與其負值都可以使用。同理,update()subtract() 也都允許 0 或負值為輸入或輸出。

  • Multiset 相關方法只為了處理正值而設計,其輸入允許是 0 或負值但只有正值會被輸出。並無型別限制,但其值的型別須支援加、減及比較運算。

  • elements() 方法需要其計數為正值,如為 0 或負值則忽略。

也參考

  • Smalltalk 中的Bag class

  • 維基百科上的多重集合條目。

  • C++ multisets 教學與範例。

  • Multiset 的數學運算及其使用時機,參考Knuth, Donald. The Art of Computer Programming Volume II, Section 4.6.3, Exercise 19

  • 若要根據給定的元素集合來列舉出所有不重複且擁有指定元素數量的 multiset,請見itertools.combinations_with_replacement()

    map(Counter,combinations_with_replacement('ABC',2))# --> AA AB AC BB BC CC

deque 物件

classcollections.deque([iterable[,maxlen]])

回傳一個新的 deque(雙端佇列)物件,將iterable 中的資料由左至右(使用append())加入來做初始化。如果iterable 並未給定,回傳的則是一個空的 deque。

Deque(發音為 "deck",為 "double-ended queue" 的簡稱)為 stack 和 queue 的一般化。deque 支援執行緒安全 (thread-safe),且能夠有效率地節省記憶體在頭和尾加入和移除元素,兩個方向的表現都大致為O(1) 複雜度。

雖然list 物件也支援類似操作,但 list 優化了長度固定時的操作,而會改變底層資料的長度及位置的pop(0)insert(0,v) 操作,記憶體移動則為O(n) 複雜度。

如果maxlen 沒有給定或者為None,deque 可以增長到任意長度;但若有給定的話,deque 的最大長度就會被限制。一個被限制長度的 deque 一但滿了,若在一端加入數個新元素,則同時會在另一端移除相同數量的元素。限定長度的 deque 提供了和 Unixtail filter 類似的功能,可用於追蹤使用者在意的那些最新執行事項或數據源。

Deque 物件支援以下方法:

append(x)

x 自 deque 的右側加入。

appendleft(x)

x 自 deque 的左側加入。

clear()

將所有元素從 deque 中移除,使其長度為 0。

copy()

建立一個 deque 的淺複製 (shallow copy)。

在 3.5 版被加入.

count(x)

計算 deque 內元素為x 的個數。

在 3.2 版被加入.

extend(iterable)

將 iterable 引數加入 deque 的右側。

extendleft(iterable)

將 iterable 引數加入 deque 的左側。要注意的是,加入後的元素順序和 iterable 參數是相反的。

index(x[,start[,stop]])

回傳 deque 中x 的位置(或在索引start 之後、索引stop 之前的位置)。回傳第一個匹配的位置,如果沒找到就引發ValueError

在 3.5 版被加入.

insert(i,x)

在 deque 位置i 中插入x

如果此插入操作導致 deque 超過其長度上限maxlen 的話,會引發IndexError 例外。

在 3.5 版被加入.

pop()

移除並回傳 deque 的最右側元素,若本來就沒有任何元素,則會引發IndexError

popleft()

移除並回傳 deque 的最左側元素,若本來就沒有任何元素,則會引發IndexError

remove(value)

移除第一個出現的value,如果沒找到的話就引發一個ValueError

reverse()

將 deque 中的元素原地 (in-place) 倒序排列並回傳None

在 3.2 版被加入.

rotate(n=1)

將 deque 向右輪轉n 步。若n 為負值則向左輪轉。

當 deque 不是空的,向右輪轉一步和d.appendleft(d.pop()) 有相同意義,而向左輪轉亦等價於d.append(d.popleft())

Deque 物件也提供了一個唯讀屬性:

maxlen

Deque 的最大長度,如果不限制長度的話則為None

在 3.1 版被加入.

除了以上使用方式,deque 亦支援了疊代、pickle、len(d)reversed(d)copy.copy(d)copy.deepcopy(d)、用in 運算子來作隸屬資格檢測以及像是d[0] 的標號引用來取得第一個元素。在兩端做索引存取的複雜度為O(1) 但越靠近中間則減慢至O(n)。若想要隨機而快速的存取,使用 list 會較為合適。

自從 3.5 版本起,deque 開始支援__add__()__mul__()__imul__()

範例:

>>>fromcollectionsimportdeque>>>d=deque('ghi')# make a new deque with three items>>>forelemind:# iterate over the deque's elements...print(elem.upper())GHI>>>d.append('j')# add a new entry to the right side>>>d.appendleft('f')# add a new entry to the left side>>>d# show the representation of the dequedeque(['f', 'g', 'h', 'i', 'j'])>>>d.pop()# return and remove the rightmost item'j'>>>d.popleft()# return and remove the leftmost item'f'>>>list(d)# list the contents of the deque['g', 'h', 'i']>>>d[0]# peek at leftmost item'g'>>>d[-1]# peek at rightmost item'i'>>>list(reversed(d))# list the contents of a deque in reverse['i', 'h', 'g']>>>'h'ind# search the dequeTrue>>>d.extend('jkl')# add multiple elements at once>>>ddeque(['g', 'h', 'i', 'j', 'k', 'l'])>>>d.rotate(1)# right rotation>>>ddeque(['l', 'g', 'h', 'i', 'j', 'k'])>>>d.rotate(-1)# left rotation>>>ddeque(['g', 'h', 'i', 'j', 'k', 'l'])>>>deque(reversed(d))# make a new deque in reverse orderdeque(['l', 'k', 'j', 'i', 'h', 'g'])>>>d.clear()# empty the deque>>>d.pop()# cannot pop from an empty dequeTraceback (most recent call last):File"<pyshell#6>",line1,in-toplevel-d.pop()IndexError:pop from an empty deque>>>d.extendleft('abc')# extendleft() reverses the input order>>>ddeque(['c', 'b', 'a'])

deque 用法

這一章節提供了多種操作 deque 的案例。

被限制長度的 deque 功能類似 Unix 中的tail filter:

deftail(filename,n=10):'Return the last n lines of a file'withopen(filename)asf:returndeque(f,n)

另一用法是透過從右邊加入、從左邊移除來維護最近加入元素的 list:

defmoving_average(iterable,n=3):# moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0# https://en.wikipedia.org/wiki/Moving_averageit=iter(iterable)d=deque(itertools.islice(it,n-1))d.appendleft(0)s=sum(d)foreleminit:s+=elem-d.popleft()d.append(elem)yields/n

一個輪詢調度器可以透過在deque 中放入 iterator 來實現,值自目前 iterator 的位置 0 取出,如果 iterator 已經消耗完畢就用popleft() 將其從佇列中移除,否則利用rotate() 來將其移至佇列尾端:

defroundrobin(*iterables):"roundrobin('ABC', 'D', 'EF') --> A D E B F C"iterators=deque(map(iter,iterables))whileiterators:try:whileTrue:yieldnext(iterators[0])iterators.rotate(-1)exceptStopIteration:# Remove an exhausted iterator.iterators.popleft()

rotate() 提供了可以用來實作deque 切片和刪除的方法。舉例來說,用純 Python 實作deld[n] 需要用rotate() 來定位要被移除的元素:

defdelete_nth(d,n):d.rotate(-n)d.popleft()d.rotate(n)

要實現deque 切片,可使用近似以下方法:使用rotate() 來將目標元素移動到 deque 最左側,用popleft() 移除舊元素並用extend() 加入新元素,最後再反向 rotate。在這個方法上做小小的更動就能簡單地實現 Forth 風格的 stack 操作,例如dupdropswapoverpickrotroll

defaultdict 物件

classcollections.defaultdict(default_factory=None,/[,...])

回傳一個新的類似字典的物件。defaultdict 是內建類別dict 的子類別。它覆蓋掉了一個方法並添加了一個可寫入的實例變數。其餘功能與dict 相同,此文件不再複述。

第一個引數為default_factory 屬性提供了初始值,他被預設為None,所有其他的引數(包括關鍵字引數)都會被傳遞給dict 的建構函式 (constructor)。

defaultdict 物件支援以下dict 所沒有的方法:

__missing__(key)

如果default_factory 屬性為None,呼叫此方法會引發一個附帶引數keyKeyError 例外。

如果default_factory 不為None,它會不帶引數地被呼叫來為給定的key 提供一個預設值,這個值和key 被作為鍵值對來插入到字典中,並且被此方法所回傳。

如果呼叫default_factory 時發生例外,則該例外將會保持不變地向外傳遞。

在無法找到所要求的鍵時,此方法會被dict 類別的__getitem__() 方法呼叫。無論此方法回傳了值還是引發了例外,都會被__getitem__() 所傳遞。

注意,__missing__()不會__getitem__() 以外的其他方法呼叫,這意味著get() 會像一般的 dict 那樣回傳None 做為預設值,而非使用default_factory

defaultdict 物件支援以下實例變數:

default_factory

此屬性為__missing__() 方法所使用。如果有引數被傳入建構函式,則此屬性會被初始化成第一個引數,如未提供引數則被初始化為None

在 3.9 版的變更:新增合併 (|) 和更新 (|=) 運算子,請見PEP 584

defaultdict 範例

使用list 作為default_factory 可以很輕鬆地將鍵值對序列轉換為包含 list 之字典:

>>>s=[('yellow',1),('blue',2),('yellow',3),('blue',4),('red',1)]>>>d=defaultdict(list)>>>fork,vins:...d[k].append(v)...>>>sorted(d.items())[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

當每個鍵第一次被存取時,它還沒有存在於對映中,所以會自動呼叫default_factory 方法來回傳一個空的list 以建立一個條目,list.append() 操作後續會再新增值到這個新的列表裡。當再次存取該鍵時,就如普通字典般操作(回傳該鍵所對應到的 list),list.append() 也會新增另一個值到 list 中。和使用與其等價的dict.setdefault() 相比,這個技巧更加快速和簡單:

>>>d={}>>>fork,vins:...d.setdefault(k,[]).append(v)...>>>sorted(d.items())[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

設定default_factoryint 使得defaultdict 可被用於計數(類似其他語言中的 bag 或 multiset):

>>>s='mississippi'>>>d=defaultdict(int)>>>forkins:...d[k]+=1...>>>sorted(d.items())[('i', 4), ('m', 1), ('p', 2), ('s', 4)]

當一個字母首次被存取時,它並不存在於對映中,則default_factory 函式會呼叫int() 來提供一個整數 0 作為預設值。後續的增加操作繼續對每個字母做計數。

函式int() 總是回傳 0,這是常數函式的特殊情況。一個更快、更有彈性的方法是使用 lambda 函式來提供任何常數值(不用一定要是 0):

>>>defconstant_factory(value):...returnlambda:value...>>>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)>>>fork,vins:...d[k].add(v)...>>>sorted(d.items())[('blue', {2, 4}), ('red', {1, 3})]

namedtuple() 擁有具名欄位之 tuple 的工廠函式

Named tuple(具名元組)賦予 tuple 中各個位置意義,使程式碼更有可讀性與自我文件性。它們可以用於任何普通 tuple 可使用的場合,賦予其透過名稱(而非位置索引)來存取欄位的能力。

collections.namedtuple(typename,field_names,*,rename=False,defaults=None,module=None)

回傳一個名為typename 的新 tuple 子類別。這個新的子類別被用於建立類似 tuple 的物件,可以透過屬性名稱來存取欄位,它同時也是可索引 (indexable) 和可疊代的 (iterable)。子類別實例同樣有文件字串 (docstring)(有類別名稱typename 和欄位名field_names)和一個好用的__repr__() 方法,可將 tuple 內容以name=value 格式列出。

field_names 是一個像['x','y'] 一樣的字串序列。field_names 也可以是一個用空白或逗號分隔各個欄位名稱的字串,比如'xy' 或者'x,y'

除了底線開頭以外的其他任何有效 Python 識別字 (identifier) 都可以作為欄位名稱,有效識別字由字母、數字、底線所組成,但不能是數字或底線開頭,也不能是關鍵詞keyword,例如classforreturnglobalpassraise

如果rename 為真值,無效的欄位名稱會自動被位置名稱取代。比如['abc','def','ghi','abc'] 會被轉換成['abc','_1','ghi','_3'],移除了關鍵字def 和重複欄位名abc

defaults 可以為None 或者是一個預設值的iterable。因為有預設值的欄位必須出現在那些沒有預設值的欄位之後,defaults 是被應用在右側的引數。例如 fieldnames 為['x','y','z'] 且 defaults 為(1,2),那麼x 就必須被給定一個引數,y 被預設為1z 則被預設為2

如果module 值有被定義,named tuple 的__module__ 屬性就被設定為該值。

Named tuple 實例中沒有字典,所以它們更加輕量,且和一般 tuple 相比佔用更少記憶體。

要支援 pickle,應將 named tuple 類別賦值給一個符合typename 的變數。

在 3.1 版的變更:新增對於rename 的支援。

在 3.6 版的變更:verboserename 參數成為僅限關鍵字引數

在 3.6 版的變更:新增module 參數。

在 3.7 版的變更:移除verbose 參數和_source 屬性。

在 3.7 版的變更:新增defaults 參數和_field_defaults 屬性。

>>># Basic example>>>Point=namedtuple('Point',['x','y'])>>>p=Point(11,y=22)# instantiate with positional or keyword arguments>>>p[0]+p[1]# indexable like the plain tuple (11, 22)33>>>x,y=p# unpack like a regular tuple>>>x,y(11, 22)>>>p.x+p.y# fields also accessible by name33>>>p# readable __repr__ with a name=value stylePoint(x=11, y=22)

Named tuple 在賦予欄位名稱於csvsqlite3 模組回傳之 tuple 時相當有用:

EmployeeRecord=namedtuple('EmployeeRecord','name, age, title, department, paygrade')importcsvforempinmap(EmployeeRecord._make,csv.reader(open("employees.csv","rb"))):print(emp.name,emp.title)importsqlite3conn=sqlite3.connect('/companydata')cursor=conn.cursor()cursor.execute('SELECT name, age, title, department, paygrade FROM employees')forempinmap(EmployeeRecord._make,cursor.fetchall()):print(emp.name,emp.title)

除了繼承自 tuple 的方法,named tuple 還支援三個額外的方法和兩個屬性。為了防止欄位名稱有衝突,方法和屬性的名稱以底線開頭。

classmethodsomenamedtuple._make(iterable)

從已存在的序列或可疊代物件建立一個新實例的類別方法。

>>>t=[11,22]>>>Point._make(t)Point(x=11, y=22)
somenamedtuple._asdict()

回傳一個將欄位名稱對映至對應值的dict

>>>p=Point(x=11,y=22)>>>p._asdict(){'x': 11, 'y': 22}

在 3.1 版的變更:回傳一個OrderedDict 而非dict

在 3.8 版的變更:回傳一個常規dict 而非OrderedDict,自從 Python 3.7 開始,dict 已經保證有順序性,如果需要OrderedDict 所專屬的特性,推薦的解法是將結果專換成所需的類型:OrderedDict(nt._asdict())

somenamedtuple._replace(**kwargs)

回傳一個新的 named tuple 實例,並將指定欄位替換為新的值:

>>>p=Point(x=11,y=22)>>>p._replace(x=33)Point(x=33, y=22)>>>forpartnum,recordininventory.items():...inventory[partnum]=record._replace(price=newprices[partnum],timestamp=time.now())

Named tuples are also supported by generic functioncopy.replace().

在 3.13 版的變更:RaiseTypeError instead ofValueError for invalidkeyword arguments.

somenamedtuple._fields

列出 tuple 欄位名稱的字串,用於自我檢查或是從現有 named tuple 建立一個新的 named tuple 型別。

>>>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)
somenamedtuple._field_defaults

將欄位名稱對映至預設值的字典。

>>>Account=namedtuple('Account',['type','balance'],defaults=[0])>>>Account._field_defaults{'balance': 0}>>>Account('premium')Account(type='premium', balance=0)

要取得這個名稱存於字串的欄位,要使用getattr() 函式:

>>>getattr(p,'x')11

(如拆解引數列表(Unpacking Argument Lists)所述)將一個字典轉換成 named tuple,要使用 ** 雙星號運算子:

>>>d={'x':11,'y':22}>>>Point(**d)Point(x=11, y=22)

因為一個 named tuple 是一個常規的 Python 類別,我們可以很容易的透過子類別來新增或更改功能,以下是如何新增一個計算得到的欄位和固定寬度的輸出列印格式:

>>>classPoint(namedtuple('Point',['x','y'])):...__slots__=()...@property...defhypot(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)>>>forpinPoint(3,4),Point(14,5/7):...print(p)Point: x= 3.000  y= 4.000  hypot= 5.000Point: x=14.000  y= 0.714  hypot=14.018

上面的子類別將__slots__ 設定為空 tuple,這樣一來就防止了字典實例被建立,因而保持了較低的記憶體用量。

子類別化無法用於增加新的、已被儲存的欄位,應當透過_fields 屬性以建立一個新的 named tuple 來實現:

>>>Point3D=namedtuple('Point3D',Point._fields+('z',))

透過直接賦值給__doc__,可以自訂說明文件字串:

>>>Book=namedtuple('Book',['id','title','authors'])>>>Book.__doc__+=': Hardcover book in active collection'>>>Book.id.__doc__='13-digit ISBN'>>>Book.title.__doc__='Title of first printing'>>>Book.authors.__doc__='List of authors sorted by last name'

在 3.5 版的變更:文件字串屬性變成可寫入。

也參考

  • 關於為 named tuple 新增型別提示的方法,請參閱typing.NamedTuple,它運用class 關鍵字以提供了一個簡潔的表示法:

    classComponent(NamedTuple):part_number:intweight:floatdescription:Optional[str]=None
  • 關於以 dict 而非 tuple 為底層的可變命名空間,請參考types.SimpleNamespace()

  • dataclasses 模組提供了一個裝飾器和一些函式,用於自動將被生成的特殊方法新增到使用者定義的類別中。

OrderedDict 物件

Ordered dictionary(有序字典)就像常規字典一樣,但有一些與排序操作相關的額外功能,但由於內建的dict 類別現在已經有記憶插入順序的能力(Python 3.7 中確保了這種新行為),它們變得不那麼重要了。

仍存在一些與dict 的不同之處:

  • 常規的dict 被設計成非常擅長於對映相關操作,追蹤插入的順序為次要目標。

  • OrderedDict 則被設計成擅長於重新排序相關的操作,空間效率、疊代速度和更新操作的效能則為次要設計目標。

  • OrderedDict 比起dict 更適合處理頻繁的重新排序操作,如在下方用法中所示,這讓它適合用於多種 LRU cache 的實作中。

  • OrderedDict 之相等性運算會檢查順序是否相同。

    一個一般的dict 可以用p==qandall(k1==k2fork1,k2inzip(p,q)) 來效仿有檢查順序的相等性運算。

  • OrderedDict 類別的popitem() 方法有不同的函式簽名 (signature),它接受傳入一個選擇性引數來指定要移除哪個元素。

    一個一般的dict 可以用d.popitem() 來效仿 OrderedDict 的od.popitem(last=True),這保證會移除最右邊(最後一個)的元素。

    一個一般的dict 可以用(k:=next(iter(d)),d.pop(k)) 來效仿 OrderedDict 的od.popitem(last=False),若最左邊(第一個)的元素存在,則將其回傳並移除。

  • OrderedDict 有個move_to_end() 方法可有效率地將一個元素重新排列到任一端。

    一個一般的dict 可以用d[k]=d.pop(k) 來效仿 OrderedDict 的od.move_to_end(k,last=True),這會將該鍵與其對應到的值移動至最右(最後面)的位置。

    一個一般的dict 沒有和 OrderedDict 的od.move_to_end(k,last=False) 等價的有效方式,這是將鍵與其對應到的值移動至最左(最前面)位置的方法。

  • 在 Python 3.8 之前,dict 並沒有__reversed__() 方法。

classcollections.OrderedDict([items])

回傳一個dict 子類別的實例,它具有專門用於重新排列字典順序的方法。

在 3.1 版被加入.

popitem(last=True)

Ordered dictionary 的popitem() 方法移除並回傳一個鍵值 (key, value) 對。如果last 為真值,則按LIFO 後進先出的順序回傳鍵值對,否則就按FIFO 先進先出的順序回傳鍵值對。

move_to_end(key,last=True)

將現有的key 移動到 ordered dictionary 的任一端。如果last 為真值(此為預設值)則將元素移至右端;如果last 為假值則將元素移至左端。如果key 不存在則會引發KeyError

>>>d=OrderedDict.fromkeys('abcde')>>>d.move_to_end('b')>>>''.join(d)'acdeb'>>>d.move_to_end('b',last=False)>>>''.join(d)'bacde'

在 3.2 版被加入.

除了普通的對映方法,ordered dictionary 還支援了透過reversed() 來做倒序疊代。

OrderedDict 物件之間的相等性運算是會檢查順序是否相同的,大致等價於list(od1.items())==list(od2.items())

OrderedDict 物件和其他Mapping 物件間的相等性運算則像普通字典一樣不考慮順序性,這使得OrderedDict 可於任何字典可使用的時機中被替換掉。

在 3.5 版的變更:OrderedDict 的項 (item)、鍵與值之視圖現在可透過reversed() 來倒序疊代。

在 3.6 版的變更:隨著PEP 468 被核可,被傳入給OrderedDict 建構函式與其update() 方法的關鍵字引數之順序被保留了下來。

在 3.9 版的變更:新增合併 (|) 和更新 (|=) 運算子,請見PEP 584

OrderedDict 範例與用法

建立一個能夠記住鍵最後插入順序的 ordered dictionary 變體很簡單。如果新條目覆蓋了現有條目,則原本插入位置會被更改並移動至末端:

classLastUpdatedOrderedDict(OrderedDict):'Store items in the order the keys were last added'def__setitem__(self,key,value):super().__setitem__(key,value)self.move_to_end(key)

OrderedDict 在實現一個functools.lru_cache() 的變形版本時也非常有用:

fromcollectionsimportOrderedDictfromtimeimporttimeclassTimeBoundedLRU:"LRU Cache that invalidates and refreshes old entries."def__init__(self,func,maxsize=128,maxage=30):self.cache=OrderedDict()# { args : (timestamp, result)}self.func=funcself.maxsize=maxsizeself.maxage=maxagedef__call__(self,*args):ifargsinself.cache:self.cache.move_to_end(args)timestamp,result=self.cache[args]iftime()-timestamp<=self.maxage:returnresultresult=self.func(*args)self.cache[args]=time(),resultiflen(self.cache)>self.maxsize:self.cache.popitem(last=False)returnresult
classMultiHitLRUCache:""" LRU cache that defers caching a result until        it has been requested multiple times.        To avoid flushing the LRU cache with one-time requests,        we don't cache until a request has been made more than once.    """def__init__(self,func,maxsize=128,maxrequests=4096,cache_after=1):self.requests=OrderedDict()# { uncached_key : request_count }self.cache=OrderedDict()# { cached_key : function_result }self.func=funcself.maxrequests=maxrequests# max number of uncached requestsself.maxsize=maxsize# max number of stored return valuesself.cache_after=cache_afterdef__call__(self,*args):ifargsinself.cache:self.cache.move_to_end(args)returnself.cache[args]result=self.func(*args)self.requests[args]=self.requests.get(args,0)+1ifself.requests[args]<=self.cache_after:self.requests.move_to_end(args)iflen(self.requests)>self.maxrequests:self.requests.popitem(last=False)else:self.requests.pop(args,None)self.cache[args]=resultiflen(self.cache)>self.maxsize:self.cache.popitem(last=False)returnresult

UserDict 物件

UserDict 類別是作為 dict 物件的包裝器。因為已經可以直接自dict 建立子類別,這個類別的需求已部分被滿足,不過這個類別使用起來更方便,因為被包裝的字典可以作為其屬性來存取。

classcollections.UserDict([initialdata])

模擬字典的類別。實例的內容被存於一個字典,可透過UserDictdata 屬性來做存取。如果有提供initialdatadata 屬性會被初始化為其值;要注意指到initialdata 的參照不會被保留,使其可被用於其他目的。

除了支援作為對映所需的方法與操作,UserDict 實例提供了以下屬性:

data

一個真實的字典,用於儲存UserDict 類別的資料內容。

UserList 物件

此類別是 list 物件的包裝器。它是個方便的基礎類別,可繼承它並覆寫現有方法或加入新方法來定義你所需的一個類似於 list 的類別。如此一來,我們可以為 list 加入新的特性。

因為已經可以直接自list 建立子類別,這個類別的需求已部分被滿足,不過這個類別使用起來更方便,因為被包裝的 list 可以作為其屬性來存取。

classcollections.UserList([list])

模擬 list 的類別。實例的內容被存於一個 list,可透過UserListdata 屬性來做存取。實例內容被初始化為list 的複製,預設為一個空的 list[]list 可以是任何 iterable,例如一個真實的 Python list 或是一個UserList 物件。

除了支援可變序列的方法與操作,UserList 實例提供了以下屬性:

data

一個真實的list 物件,用於儲存UserList 類別的資料內容。

子類別化的條件:UserList 的子類別應該要提供一個不需要引數或一個引數的建構函式。回傳一個新序列的 list 操作會從那些實作出來的類別建立一個實例,為了達成上述目的,它假設建構函式可傳入單一參數來呼叫,該參數即是做為數據來源的一個序列物件。

如果希望一個自此獲得的子類別不遵從上述要求,那所有該類別支援的特殊方法則必須被覆寫;請參考原始碼來理解在這情況下哪些方法是必須提供的。

UserString 物件

UserString 類別是字串物件的包裝器,因為已經可以從str 直接建立子類別,這個類別的需求已經部分被滿足,不過這個類別使用起來更方便,因為被包裝的字串可以作為其屬性來存取。

classcollections.UserString(seq)

模擬字串物件的類別。實例的內容被存於一個字串物件,可透過UserStringdata 屬性來做存取。實例內容被初始化為seq 的複製,seq 引數可以是任何可被內建函式str() 轉換成字串的物件。

除了支援字串的方法和操作以外,UserString 實例也提供了以下屬性:

data

一個真實的str 物件,用來儲存UserString 類別的資料內容。

在 3.5 版的變更:新增方法__getnewargs____rmod__casefoldformat_mapisprintable 以及maketrans