深度解析Python collections庫
Python 標準庫里藏著不少"寶藏",collections 就是其中最值得深挖的一個。它不是什么花哨的第三方包,而是隨 Python 一起安裝、隨時可用的內置模塊——卻偏偏被大量開發(fā)者長期忽視,習慣性地用普通 dict、list 硬撐所有場景。這篇文章就來系統(tǒng)梳理 collections 里的每一個數(shù)據(jù)類型,聊聊它們的設計邏輯、使用姿勢,以及真正適合它們發(fā)光發(fā)熱的場景。
一、namedtuple— 給元組起個名字,世界清晰多了
普通元組的問題人人都遇到過:point[0] 到底是 x 還是 y?三個月后回來看代碼,完全不知道自己在寫什么。namedtuple 就是為了解決這個痛點而生的。
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 5)
print(p.x, p.y) # 3 5
print(p[0]) # 3,依然支持索引訪問
它本質上是元組的子類,不可變、內存緊湊,卻擁有字段名訪問的可讀性。和普通 class 相比,它不需要寫 __init__,也不需要 __repr__,天生就是"數(shù)據(jù)容器"的最佳形態(tài)。
核心特性
_asdict()方法可以直接轉成OrderedDict(Python 3.8+ 返回普通 dict)_replace()返回一個修改了某字段的新實例,原對象不變_fields屬性列出所有字段名- 支持設置
defaults參數(shù),給字段加默認值
Employee = namedtuple('Employee', ['name', 'dept', 'salary'], defaults=[50000])
e = Employee('Alice', 'Engineering')
print(e.salary) # 50000
適用場景
最典型的用途是替代輕量級數(shù)據(jù)類。比如解析 CSV 文件的每一行、表示數(shù)據(jù)庫查詢結果的一條記錄、封裝坐標/顏色/配置參數(shù)等。當你發(fā)現(xiàn)自己在寫 return (x, y, z) 然后調用方要靠注釋才能理解每個位置的含義時,就是換 namedtuple 的時候了。
Python 3.7 之后 dataclass 崛起,提供了可變性和更多功能,但 namedtuple 的不可變性和極低內存開銷在某些場景下依然是無可替代的優(yōu)勢。
二、deque— 雙端隊列,列表的高性能替代品
list 在尾部追加元素很快,但在頭部插入或刪除,時間復雜度是 O(n)O(n)O(n)——因為所有元素都要往后移。數(shù)據(jù)量一大,這個代價就很明顯了。deque(發(fā)音 "deck")用雙向鏈表結構解決了這個問題,兩端操作都是 O(1)O(1)O(1)。
from collections import deque dq = deque([1, 2, 3]) dq.appendleft(0) # 左端追加 dq.append(4) # 右端追加 dq.popleft() # 左端彈出 dq.pop() # 右端彈出 print(dq) # deque([1, 2, 3])
maxlen參數(shù):自動滾動的固定窗口
這是 deque 最迷人的特性之一。設置 maxlen 后,隊列滿了再追加新元素,舊元素會自動從另一端被擠出去,完全不需要手動管理。
recent = deque(maxlen=5)
for i in range(10):
recent.append(i)
print(recent) # deque([5, 6, 7, 8, 9], maxlen=5)
適用場景
- 實現(xiàn)隊列(BFS)和棧:比用
list模擬隊列性能好得多 - 滑動窗口:日志系統(tǒng)保留最近 N 條記錄、實時數(shù)據(jù)流的移動平均
- 撤銷/重做功能:用
maxlen限制歷史記錄條數(shù) - 生產者-消費者模型:配合
rotate()方法做循環(huán)緩沖區(qū)
# rotate 示例:向右旋轉 dq = deque([1, 2, 3, 4, 5]) dq.rotate(2) print(dq) # deque([4, 5, 1, 2, 3])
三、Counter— 計數(shù)這件事,它是專業(yè)的
統(tǒng)計詞頻、計算元素出現(xiàn)次數(shù)——這類需求幾乎每個項目都會遇到。手寫一個 for 循環(huán)加 if key in dict 當然能實現(xiàn),但 Counter 讓這件事優(yōu)雅得多。
from collections import Counter
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
c = Counter(words)
print(c) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
直接傳入任何可迭代對象,甚至字符串,立刻得到計數(shù)結果。
那些讓人驚喜的方法
most_common(n) 返回出現(xiàn)頻率最高的 n 個元素,底層用堆實現(xiàn),效率很高:
print(c.most_common(2)) # [('apple', 3), ('banana', 2)]
Counter 之間可以直接做算術運算,這個特性相當強大:
c1 = Counter({'a': 3, 'b': 2})
c2 = Counter({'a': 1, 'b': 4, 'c': 1})
print(c1 + c2) # Counter({'b': 6, 'a': 4, 'c': 1})
print(c1 - c2) # Counter({'a': 2}) # 負數(shù)結果被丟棄
print(c1 & c2) # Counter({'a': 1, 'b': 2}) # 取最小值(交集)
print(c1 | c2) # Counter({'b': 4, 'a': 3, 'c': 1}) # 取最大值(并集)
elements() 方法將計數(shù)展開成迭代器:
list(Counter({'a': 2, 'b': 3}).elements())
# ['a', 'a', 'b', 'b', 'b']
適用場景
- NLP 文本分析:詞頻統(tǒng)計、詞云數(shù)據(jù)準備
- 數(shù)據(jù)去重計數(shù):日志分析、用戶行為統(tǒng)計
- 投票/排名系統(tǒng):快速找出最熱門的 N 項
- 差異比較:兩個文本之間的詞匯差異
- 字謎檢測:判斷兩個字符串是否是字母異位詞,
Counter(s1) == Counter(s2)一行搞定
四、defaultdict— 再也不用寫if key not in dict
寫過這樣的代碼嗎?
# 傳統(tǒng)寫法,繁瑣
result = {}
for word in words:
if word not in result:
result[word] = []
result[word].append(something)
defaultdict 把這個模式徹底簡化了。它在訪問不存在的鍵時,自動調用你指定的工廠函數(shù)創(chuàng)建默認值,不會拋出 KeyError。
from collections import defaultdict
result = defaultdict(list)
for word in words:
result[word].append(something) # 直接用,不需要判斷
工廠函數(shù)可以是任何可調用對象:
dd_int = defaultdict(int) # 默認值 0 dd_list = defaultdict(list) # 默認值 [] dd_set = defaultdict(set) # 默認值 set() dd_str = defaultdict(str) # 默認值 '' # 也可以用 lambda 自定義 dd_custom = defaultdict(lambda: 'N/A')
構建嵌套結構
defaultdict 在構建多層嵌套字典時尤其好用:
# 構建圖的鄰接表
graph = defaultdict(list)
edges = [('A', 'B'), ('A', 'C'), ('B', 'D')]
for u, v in edges:
graph[u].append(v)
# 二維分組統(tǒng)計
sales = defaultdict(lambda: defaultdict(int))
sales['Q1']['北京'] += 100
sales['Q1']['上海'] += 200
適用場景
- 分組聚合:按某字段將數(shù)據(jù)分組,比
groupby更靈活 - 圖算法:構建鄰接表是最經典的用法
- 詞頻統(tǒng)計的另一種寫法:
defaultdict(int)配合+=1 - 緩存/記憶化:配合
lambda做簡單的懶加載
五、OrderedDict— 有序字典的歷史使命與現(xiàn)代價值
Python 3.7 之后,普通 dict 已經按插入順序保存鍵了,這讓很多人覺得 OrderedDict 已經過時。但它依然有幾個獨特的能力,是普通 dict 給不了的。
from collections import OrderedDict od = OrderedDict() od['a'] = 1 od['b'] = 2 od['c'] = 3
move_to_end()方法
這是 OrderedDict 獨有的殺手锏:
od.move_to_end('a') # 把 'a' 移到末尾
od.move_to_end('c', last=False) # 把 'c' 移到開頭
相等性比較的差異
普通 dict 的相等性只看鍵值對是否一致,不管順序;OrderedDict 則順序不同就不相等:
d1 = {'a': 1, 'b': 2}
d2 = {'b': 2, 'a': 1}
print(d1 == d2) # True
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2) # False
適用場景
OrderedDict 最經典的應用是實現(xiàn) LRU 緩存(最近最少使用緩存):
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key) # 訪問后移到末尾(最近使用)
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # 淘汰最久未使用的
當然,Python 3.2 之后 functools.lru_cache 裝飾器已經內置了這個功能,但手動實現(xiàn) LRU 時 OrderedDict 依然是最優(yōu)雅的選擇。
六、ChainMap— 多個字典,一個視圖
ChainMap 把多個字典"鏈"在一起,形成一個邏輯上的單一映射。查找時按順序逐個字典搜索,找到第一個匹配就返回;寫入操作只作用于第一個字典。
from collections import ChainMap
defaults = {'color': 'red', 'user': 'guest', 'timeout': 30}
env_vars = {'user': 'admin', 'timeout': 60}
cli_args = {'color': 'blue'}
config = ChainMap(cli_args, env_vars, defaults)
print(config['color']) # 'blue'(來自 cli_args)
print(config['user']) # 'admin'(來自 env_vars)
print(config['timeout']) # 60(來自 env_vars)
這個優(yōu)先級機制非常直觀:越靠前的字典優(yōu)先級越高。
與dict.update()的本質區(qū)別
用 {**defaults, **env_vars, **cli_args} 合并字典會創(chuàng)建一個全新的字典,原始數(shù)據(jù)的修改不會反映進來。ChainMap 是視圖,原始字典的變化會實時體現(xiàn):
config = ChainMap(cli_args, defaults) defaults['timeout'] = 100 print(config['timeout']) # 100,實時更新
適用場景
- 配置系統(tǒng):命令行參數(shù) > 環(huán)境變量 > 配置文件 > 默認值,這種優(yōu)先級層疊是
ChainMap的天然用途 - 模板引擎的變量作用域:局部變量覆蓋全局變量
- Python 解釋器內部:
locals()和globals()的關系就是類似的鏈式查找 - A/B 測試配置:不同用戶群體使用不同配置層
七、UserDict、UserList、UserString— 繼承的正確姿勢
這三個類乍看之下有點奇怪——為什么要有 UserDict,直接繼承 dict 不行嗎?
答案是:直接繼承內置類型有坑。dict 的某些方法(比如 __setitem__)在內部調用時不一定會走你重寫的版本,導致行為不一致。UserDict 是用純 Python 實現(xiàn)的字典包裝器,內部所有方法都會正確地調用彼此,繼承它來自定義字典行為更安全可靠。
from collections import UserDict
class UpperDict(UserDict):
"""鍵自動轉大寫的字典"""
def __setitem__(self, key, value):
super().__setitem__(key.upper(), value)
ud = UpperDict()
ud['hello'] = 'world'
print(ud) # {'HELLO': 'world'}
print(ud['HELLO']) # 'world'
同理,UserList 適合自定義列表行為:
from collections import UserList
class BoundedList(UserList):
"""有最大長度限制的列表"""
def __init__(self, max_size, *args):
self.max_size = max_size
super().__init__(*args)
def append(self, item):
if len(self.data) < self.max_size:
super().append(item)
else:
raise ValueError(f"列表已滿,最多 {self.max_size} 個元素")
適用場景
- 自定義數(shù)據(jù)驗證:插入時自動校驗類型或范圍
- 只讀字典/列表:重寫寫入方法拋出異常
- 帶日志的容器:每次修改自動記錄日志
- 類型強制轉換:插入時自動轉換數(shù)據(jù)格式
八、全局視角:選哪個?
梳理完七個數(shù)據(jù)結構,用一張表來幫助快速定位:
| 數(shù)據(jù)類型 | 核心特性 | 最典型用途 | 性能亮點 |
|---|---|---|---|
| namedtuple | 不可變、有字段名的元組 | 輕量數(shù)據(jù)類、函數(shù)多返回值 | 內存占用與元組相同 |
| deque | 雙端 O(1)O(1)O(1) 操作 | 隊列、滑動窗口、BFS | 兩端操作遠優(yōu)于 list |
| Counter | 自動計數(shù) + 集合運算 | 詞頻、排名、差異比較 | most_common 用堆優(yōu)化 |
| defaultdict | 自動創(chuàng)建默認值 | 分組聚合、圖鄰接表 | 省去 KeyError 判斷 |
| OrderedDict | 有序 + move_to_end | LRU 緩存、順序敏感比較 | move_to_end 是 O(1)O(1)O(1) |
| ChainMap | 多字典鏈式查找視圖 | 配置優(yōu)先級、作用域鏈 | 無需復制數(shù)據(jù) |
| UserDict/List/String | 安全繼承內置類型 | 自定義容器行為 | 繼承語義完整可靠 |
寫在最后
collections 的設計哲學其實很簡單:為常見的編程模式提供專用工具。每一個數(shù)據(jù)結構背后都對應著一類反復出現(xiàn)的需求——計數(shù)、滑動窗口、分組、配置層疊……與其每次都從零開始用基礎類型拼湊,不如直接用這些經過充分測試、性能優(yōu)化的專用容器。
代碼的質量不只體現(xiàn)在邏輯正確,還體現(xiàn)在用對了工具??吹?Counter 的時候不再手寫計數(shù)循環(huán),看到 deque 的時候不再用 list.insert(0, x)——這種直覺,是 Python 開發(fā)者成長路上很重要的一步。
到此這篇關于深度解析Python collections庫的文章就介紹到這了,更多相關Python collections庫內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
python 去除二維數(shù)組/二維列表中的重復行方法
今天小編就為大家分享一篇python 去除二維數(shù)組/二維列表中的重復行方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-01-01
Python網絡編程之socket與socketserver
這篇文章介紹了Python網絡編程之socket與socketserver,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05
Django model 中設置聯(lián)合約束和聯(lián)合索引的方法
今天小編就為大家分享一篇Django model 中設置聯(lián)合約束和聯(lián)合索引的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08

