你不知道的Python容器

i發表於2022-04-25

你不知道的Python容器

昨天閱讀了《Python Tricks: The Book》的第五章“Common Data Structures in Python”,該章節介紹了字典、陣列、集合、棧、佇列以及堆等資料結構的用法和注意事項,其中ChainMap、MappingProxyType等不常使用的容器類引起了我的注意。本文主要對幾種不常使用的容器類進行介紹,通過示例說明這些容器的特點。

雜湊表

ChainMap

“collections.ChainMap”能有效整合多個字典的資訊,提供一個可進行查詢和更新的檢視。它使用列表管理字典的引用,而非建立一個新的字典,因此時空開銷比較小。當查詢字典內容時,ChainMap按序遍歷字典列表,直至找到給定的鍵;當更新(包括新增、刪除)字典內容時,ChainMap只在字典列表的首項上進行操作。

由於ChainMap是從前到後依次查詢字典列表,如果有多個字典包含了相同的鍵,那麼靠近列表起始位置的字典就會被優先匹配,而靠近列表末端的字典會被忽略。ChainMap能夠為鍵查詢操作設定優先順序的特性使得它能夠用於管理應用程式的配置項。具體來說,當應用程式具有多種來源(命令列、配置檔案、環境變數等)的配置時,可以把優先順序高的配置當作ChainMap的第一個位置引數:

from collections import ChainMap

cli_config = {  # 命令列引數
    'batch_size': 128,  # 批次大小為128
    'learning_rate': 1e-3,
    'dropout': 0.2,
}
default_config = {  # 預設配置
    'batch_size': 64,  # 這裡也配置了批次大小
    'num_layers': 2,
}

lookup = ChainMap(cli_config, default_config)
lookup['batch_size']  # 128,命令列引數優於匹配
lookup['num_layers']  # 2,預設配置也能查詢

lookup.maps  # ChainMap({'batch_size': 128, 'learning_rate': 0.001, 'dropout': 0.2}, {'batch_size': 64, 'num_layers': 2})

lookup['beam_size'] = 4  # 新增鍵值對
del lookup['batch_size']  # 刪除鍵值對
cli_config  # {'learning_rate': 0.001, 'dropout': 0.2, 'beam_size': 4},cli_config是字典列表的首項,更新操作會作用到它身上

MappingProxyType

“types.MappingProxyType”通過代理模式控制了對字典內容的訪問,它接收一個字典作為引數,返回該字典的只讀(Read Only)檢視,任何嘗試更新該檢視的操作都會觸發“TypeError”異常。

from types import MappingProxyType

meetings = {
    'ACL': 'Annual Meeting of the Association for Computational Linguistics',
    'CVPR': 'IEEE Conference on Computer Vision and Pattern Recognition',
}
mapping = MappingProxyType(meetings)
mapping['ACL']  # 'Annual Meeting of...'
del mapping['ACL']  # TypeError: 'mappingproxy' object does not support item deletion
mapping['ACL'] = ''  # TypeError: 'mappingproxy' object does not support item assignment

線性表

Python的內建型別“list”支援按位置插入、刪除列表元素,基於它可以實現棧和佇列等操作受限的線性表,但考慮到“list”本身是基於動態陣列的,頻繁增刪列表元素會使得“list”需要時常調整佔用的儲存空間(擴容、縮容),最終導致程式效能的下降。

“collections.deque”是實現棧和佇列的更好選擇,它基於雙向連結串列,支援快速地從序列兩端新增或刪除元素。

  1. 使用deque模擬棧“後進先出”的特性

    from collections import deque
    
    stack = deque()
    stack.append('F')
    stack.append('E')
    
    stack.pop()  # 'E',首先彈出'E'
    stack.pop()  # 'F',出棧順序與入棧順序相反
    stack.pop()  # IndexError: pop from an empty deque
    
  2. 使用deque模擬佇列“先進先出”的特性

    from collections import deque
    
    queue = deque()
    queue.append('F')
    queue.append('E')
    
    queue.popleft()  # 'F','F'首先出隊
    queue.popleft()  # 'E',出隊順序與入隊順序相同
    queue.popleft()  # IndexError: pop from an empty deque
    

二叉堆是一棵完全二叉樹,有最小堆和最大堆兩種型別,其中最小堆的每個節點都小於等於它的子節點。Python的“heapq”模組提供了關於構造最小堆、插入刪除元素並保持最小堆特性的介面。

import random
import heapq

digits = [random.randrange(100) for i in range(5)]  # [75, 28, 93, 79, 57]
heapq.nsmallest(2, digits)  # [28, 57],獲取列表中最小的2個元素

heapq.heapify(digits)  # 堆化,[28, 57, 93, 79, 75]
heapq.heappush(digits, 10)  # 插入一個元素,[10, 57, 28, 79, 75, 93]
heapq.heappop(digits)  # 10,彈出最小的元素

“queue.Priority”給出了優先佇列的一種實現,它基於最小堆:

from queue import PriorityQueue

q.put((3, 'Sing'))
q.put((1, 'Jump'))
q.put((2, 'Rap'))

while not q.empty():
    next_item = q.get()
    print(next_item)

# (1, 'Jump'), (2, 'Rap'), (3, 'Sing')

參考資料

  1. Python Tricks: The Book
  2. Python's ChainMap: Manage Multiple Contexts Effectively
  3. 《流暢的Python》,2.9.4節“雙向佇列和其他形式的佇列”

相關文章