Python入門:ChainMap 有效管理多個上下文

華為雲開發者社群發表於2021-08-16
摘要: Python的ChainMap從collections模組提供用於管理多個詞典作為單個的有效工具。

本文分享自華為雲社群《從零開始學python | ChainMap 有效管理多個上下文》,作者: Yuchuan 。

有時,當您使用多個不同的詞典時,您需要將它們作為一個進行分組和管理。在其他情況下,您可以擁有多個代表不同範圍或上下文的字典,並且需要將它們作為單個字典來處理,以便您可以按照給定的順序或優先順序訪問底層資料。在這種情況下,你可以利用Python的的ChainMap從collections模組。

ChainMap將多個字典和對映組合在一個具有字典式行為的可更新檢視中。此外,ChainMap還提供了允許您有效管理各種詞典、定義關鍵查詢優先順序等的功能。

在本教程中,您將學習如何:

  • 在 Python 程式中建立ChainMap例項
  • 探索差異之間ChainMap和dict
  • 用於ChainMap將多個字典合二為一
  • 管理鍵查詢優先使用ChainMap

為了充分利用本教程,您應該瞭解在 Python 中使用字典和列表的基礎知識。

在旅程結束時,您將找到一些實際示例,這些示例將幫助您更好地瞭解ChainMap.

Python 入門 ChainMap

Python的ChainMap加入collections中的Python 3.3作為管理多一個方便的工具範圍環境。此類允許您將多個字典和其他對映組合在一起,使它們在邏輯上顯示並表現為一個整體。它建立一個單一的可更新檢視,其工作方式類似於常規字典,但有一些內部差異。

ChainMap不會將其對映合併在一起。相反,它將它們儲存在一個內部對映列表中。然後ChainMap在該列表的頂部重新實現常見的字典操作。由於內部列表儲存對原始輸入對映的引用,因此這些對映中的任何更改都會影響整個ChainMap物件。

將輸入對映儲存在列表中允許您在給定的鏈對映中擁有重複的鍵。如果您執行鍵查詢,則ChainMap搜尋對映列表,直到找到第一次出現的目標鍵。如果金鑰丟失,那麼您會KeyError像往常一樣得到一個。

當您需要管理巢狀作用域時,將對映儲存在列表中會真正發揮作用,其中每個對映代表一個特定的作用域或上下文。

為了更好地理解作用域和上下文的含義,請考慮 Python 如何解析名稱。當 Python 查詢名稱時,它會在locals()、globals()、 中搜尋,最後builtins直到找到第一次出現的目標名稱。如果名稱不存在,那麼您會得到一個NameError. 處理範圍和上下文是您可以解決的最常見的問題ChainMap。

當您使用 時ChainMap,您可以使用不相交或相交的鍵連結多個字典。

在第一種情況下,ChainMap允許您將所有字典視為一個。因此,您可以像使用單個字典一樣訪問鍵值對。在第二種情況下,除了將字典作為一個來管理之外,您還可以利用內部對映列表為字典中的重複鍵定義某種訪問優先順序。這就是為什麼ChainMap物件非常適合處理多個上下文。

一個奇怪的行為ChainMap是突變,例如更新、新增、刪除、清除和彈出鍵,僅作用於內部對映列表中的第一個對映。以下是主要功能的摘要ChainMap:

  • 從多個輸入對映構建可更新檢視
  • 提供與字典幾乎相同的介面,但具有一些額外的功能
  • 不合並輸入對映,而是將它們儲存在內部公共列表中
  • 檢視輸入對映的外部變化
  • 可以包含具有不同值的重複鍵
  • 通過內部對映列表按順序搜尋鍵
  • 在搜尋整個對映列表後KeyError缺少鍵時丟擲 a
  • 僅對內部列表中的第一個對映執行更改

在本教程中,您將詳細瞭解ChainMap. 以下部分將指導您瞭解如何ChainMap在您的程式碼中建立新例項。

例項化 ChainMap

要ChainMap在您的 Python 程式碼中建立,您首先需要從該類匯入collections,然後像往常一樣呼叫它。類初始值設定項可以將零個或多個對映作為引數。沒有引數,它初始化一個鏈對映,裡面有一個空字典:

>>> from collections import ChainMap
>>> from collections import OrderedDict, defaultdict

>>> # Use no arguments
>>> ChainMap()
ChainMap({})

>>> # Use regular dictionaries
>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}

>>> ChainMap(numbers, letters)
ChainMap({'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})

>>> ChainMap(numbers, {"a": "A", "b": "B"})
ChainMap({'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})

>>> # Use other mappings
>>> numbers = OrderedDict(one=1, two=2)
>>> letters = defaultdict(str, {"a": "A", "b": "B"})
>>> ChainMap(numbers, letters)
ChainMap(
    OrderedDict([('one', 1), ('two', 2)]),
    defaultdict(<class 'str'>, {'a': 'A', 'b': 'B'})
)

在這裡,您ChainMap使用不同的對映組合建立多個物件。在每種情況下,ChainMap返回所有輸入對映的單個類似字典的檢視。請注意,您可以使用任何型別的對映,例如OrderedDict和defaultdict。

您還可以ChainMap使用類方法 建立物件.fromkeys()。此方法可以採用可迭代的鍵和所有鍵的可選預設值:

>>> from collections import ChainMap

>>> ChainMap.fromkeys(["one", "two","three"])
ChainMap({'one': None, 'two': None, 'three': None})

>>> ChainMap.fromkeys(["one", "two","three"], 0)
ChainMap({'one': 0, 'two': 0, 'three': 0})

如果你呼叫.fromkeys()上ChainMap與迭代鍵作為引數,那麼你得到的鏈條地圖一個字典。鍵來自輸入可迭代物件,值預設為None。或者,您可以傳遞第二個引數 來.fromkeys()為每個鍵提供合理的預設值。

執行類似字典的操作

ChainMap支援與常規字典相同的 API,用於訪問現有金鑰。擁有ChainMap物件後,您可以使用字典樣式的鍵查詢來檢索現有鍵,或者您可以使用.get():

>>> from collections import ChainMap

>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}

>>> alpha_num = ChainMap(numbers, letters)
>>> alpha_num["two"]
2

>>> alpha_num.get("a")
'A'

>>> alpha_num["three"]
Traceback (most recent call last):
    ...
KeyError: 'three'

鍵查詢搜尋目標鏈對映中的所有對映,直到找到所需的鍵。如果金鑰不存在,那麼您將獲得通常的KeyError. 現在,當您有重複的鍵時,查詢操作的行為如何?在這種情況下,您將獲得第一次出現的目標鍵:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"dogs": 4, "cats": 3, "turtles": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)

>>> pets["dogs"]
10
>>> pets.get("cats")
7
>>> pets["turtles"]
1

當您訪問重複鍵(例如"dogs"and )時"cats",鏈對映僅返回該鍵的第一次出現。在內部,查詢操作按照它們在內部對映列表中出現的相同順序搜尋輸入對映,這也是您將它們傳遞到類的初始值設定項的確切順序。

這種一般行為也適用於迭代:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"dogs": 4, "cats": 3, "turtles": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)

>>> for key, value in pets.items():
...     print(key, "->", value)
...
dogs -> 10
cats -> 7
turtles -> 1
pythons -> 3

該for迴圈遍歷在字典pets和列印每個鍵-值對的第一次出現。您還可以直接或使用and遍歷字典.keys(),.values()就像使用任何字典一樣:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"dogs": 4, "cats": 3, "turtles": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)

>>> for key in pets:
...     print(key, "->", pets[key])
...
dogs -> 10
cats -> 7
turtles -> 1
pythons -> 3

>>> for key in pets.keys():
...     print(key, "->", pets[key])
...
dogs -> 10
cats -> 7
turtles -> 1
pythons -> 3

>>> for value in pets.values():
...     print(value)
...
10
7
1
3

同樣,行為是相同的。每次迭代都經過底層鏈對映中每個鍵、項和值的第一次出現。

ChainMap還支援突變。換句話說,它允許您更新、新增、刪除和彈出鍵值對。這種情況下的不同之處在於這些操作僅作用於第一個對映:

>>> from collections import ChainMap

>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}

>>> alpha_num = ChainMap(numbers, letters)
>>> alpha_num
ChainMap({'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})

>>> # Add a new key-value pair
>>> alpha_num["c"] = "C"
>>> alpha_num
ChainMap({'one': 1, 'two': 2, 'c': 'C'}, {'a': 'A', 'b': 'B'})

>>> # Update an existing key
>>> alpha_num["b"] = "b"
>>> alpha_num
ChainMap({'one': 1, 'two': 2, 'c': 'C', 'b': 'b'}, {'a': 'A', 'b': 'B'})

>>> # Pop keys
>>> alpha_num.pop("two")
2
>>> alpha_num.pop("a")
Traceback (most recent call last):
    ...
KeyError: "Key not found in the first mapping: 'a'"

>>> # Delete keys
>>> del alpha_num["c"]
>>> alpha_num
ChainMap({'one': 1, 'b': 'b'}, {'a': 'A', 'b': 'B'})
>>> del alpha_num["a"]
Traceback (most recent call last):
    ...
KeyError: "Key not found in the first mapping: 'a'"

>>> # Clear the dictionary
>>> alpha_num.clear()
>>> alpha_num
ChainMap({}, {'a': 'A', 'b': 'B'})

改變給定鏈對映內容的操作只會影響第一個對映,即使您嘗試改變的鍵存在於列表中的其他對映中。例如,當您嘗試"b"在第二個對映中進行更新時,真正發生的是您向第一個字典新增了一個新鍵。

您可以利用此行為來建立不修改原始輸入字典的可更新鏈對映。在這種情況下,您可以使用空字典作為第一個引數ChainMap:

>>> from collections import ChainMap

>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}

>>> alpha_num = ChainMap({}, numbers, letters)
>>> alpha_num
ChainMap({}, {'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})

>>> alpha_num["comma"] = ","
>>> alpha_num["period"] = "."

>>> alpha_num
ChainMap(
    {'comma': ',', 'period': '.'},
    {'one': 1, 'two': 2},
    {'a': 'A', 'b': 'B'}
)

在這裡,您使用一個空字典 ( {}) 建立alpha_num. 這確保您執行的更改alpha_num永遠不會影響您的兩個原始輸入字典numbers和letters,並且只會影響列表開頭的空字典。

合併與連結字典

作為使用 連結多個字典的替代方法ChainMap,您可以考慮使用dict.update()以下方法將它們合併在一起:

>>> from collections import ChainMap

>>> # Chain dictionaries with ChainMap
>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"hamsters": 2, "turtles": 1}

>>> ChainMap(for_adoption, vet_treatment)
ChainMap(
    {'dogs': 10, 'cats': 7, 'pythons': 3},
    {'hamsters': 2, 'turtles': 1}
)

>>> # Merge dictionaries with .update()
>>> pets = {}
>>> pets.update(for_adoption)
>>> pets.update(vet_treatment)
>>> pets
{'dogs': 10, 'cats': 7, 'pythons': 3, 'hamsters': 2, 'turtles': 1}

在此特定示例中,當您從兩個具有唯一鍵的現有字典構建鏈對映和等效字典時,您會得到類似的結果。

與將字典.update()與ChainMap. 第一個也是最重要的缺點是,您無法使用多個範圍或上下文來管理和確定對重複鍵的訪問的優先順序。使用.update(),您為給定鍵提供的最後一個值將始終佔上風:

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"cats": 2, "dogs": 1}

>>> # Merge dictionaries with .update()
>>> pets = {}
>>> pets.update(for_adoption)
>>> pets.update(vet_treatment)
>>> pets
{'dogs': 1, 'cats': 2, 'pythons': 3}

常規詞典不能儲存重複的鍵。每次呼叫.update()現有鍵的值時,該鍵都會更新為新值。在這種情況下,您將無法使用不同的範圍對重複金鑰的訪問進行優先順序排序。

注意:從Python 3.5 開始,您還可以使用字典解包運算子 ( **)將字典合併在一起。此外,如果您使用的是Python 3.9,那麼您可以使用字典聯合運算子 ( |) 將兩個字典合併為一個新字典。

現在假設您有n 個不同的對映,每個對映最多有m 個鍵。ChainMap從它們建立物件將花費O ( n )執行時間,而在最壞的情況下檢索鍵將花費O ( n ),其中目標鍵位於內部對映列表的最後一個字典中。

或者,.update()在迴圈中建立一個常規字典需要O ( nm ),而從最終字典中檢索一個鍵需要O (1)。

結論是,如果您經常建立字典鏈並且每次只執行幾個鍵查詢,那麼您應該使用ChainMap. 如果是相反的方式,則使用常規詞典,除非您需要重複的鍵或多個範圍。

合併字典和連結字典之間的另一個區別是,當您使用 時ChainMap,輸入字典中的外部更改會影響基礎鏈,而合併字典的情況並非如此。

探索附加功能 ChainMap

ChainMap提供與常規 Python 字典大致相同的 API 和功能,但有一些您已經知道的細微差別。ChainMap還支援一些特定於其設計和目標的附加功能。

在本節中,您將瞭解所有這些附加功能。當您訪問字典中的鍵值對時,您將瞭解它們如何幫助您管理不同的範圍和上下文。

管理對映列表 .maps

ChainMap將所有輸入對映儲存在一個內部列表中。此列表可通過名為的公共例項屬性訪問.maps,並且可由使用者更新。中的對映.maps順序與您將它們傳遞到 中的順序相匹配ChainMap。此順序定義執行鍵查詢操作時的搜尋順序。

以下是您如何訪問的示例.maps:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"dogs": 4, "turtles": 1}

>>> pets = ChainMap(for_adoption, vet_treatment)
>>> pets.maps
[{'dogs': 10, 'cats': 7, 'pythons': 3}, {'dogs': 4, 'turtles': 1}]

在這裡,您用於.maps訪問pets儲存的對映的內部列表。此列表是常規 Python 列表,因此您可以手動新增和刪除對映、遍歷列表、更改對映的順序等:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"cats": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)

>>> pets.maps.append({"hamsters": 2})
>>> pets.maps
[{'dogs': 10, 'cats': 7, 'pythons': 3}, {"cats": 1}, {'hamsters': 2}]

>>> del pets.maps[1]
>>> pets.maps
[{'dogs': 10, 'cats': 7, 'pythons': 3}, {'hamsters': 2}]

>>> for mapping in pets.maps:
...     print(mapping)
...
{'dogs': 10, 'cats': 7, 'pythons': 3}
{'hamsters': 2}

在這些示例中,您首先將一個新字典新增到.mapsusing 中.append()。然後使用del 關鍵字刪除位置 處的字典1。您可以.maps像管理任何常規 Python 列表一樣進行管理。

注意:對映的內部列表.maps將始終包含至少一個對映。例如,如果您使用ChainMap()不帶引數建立一個空鏈對映,那麼該列表將儲存一個空字典。

您可以.maps在對所有對映執行操作時對其進行迭代。遍歷對映列表的可能性允許您對每個對映執行不同的操作。使用此選項,您可以解決僅更改列表中的第一個對映的預設行為。

一個有趣的例子是,您可以使用以下命令顛倒當前對映列表的順序.reverse():

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"cats": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)
>>> pets
ChainMap({'dogs': 10, 'cats': 7, 'pythons': 3}, {'cats': 1})

>>> pets.maps.reverse()
>>> pets
ChainMap({'cats': 1}, {'dogs': 10, 'cats': 7, 'pythons': 3})

反轉內部對映列表允許您在查詢鏈對映中的給定鍵時反轉搜尋順序。現在,當您查詢 時"cats",您會得到接受獸醫治療的貓的數量,而不是準備收養的貓的數量。

新增新的子上下文 .new_child()

ChainMap也實現.new_child(). 此方法可選擇將對映作為引數並返回一個新ChainMap例項,該例項包含輸入對映,後跟底層鏈對映中的所有當前對映:

>>> from collections import ChainMap

>>> mom = {"name": "Jane", "age": 31}
>>> dad = {"name": "John", "age": 35}

>>> family = ChainMap(mom, dad)
>>> family
ChainMap({'name': 'Jane', 'age': 31}, {'name': 'John', 'age': 35})

>>> son = {"name": "Mike", "age": 0}
>>> family = family.new_child(son)

>>> for person in family.maps:
...     print(person)
...
{'name': 'Mike', 'age': 0}
{'name': 'Jane', 'age': 31}
{'name': 'John', 'age': 35}

在這裡,.new_child()返回一個ChainMap包含新對映的新物件son,後跟舊對映,mom和dad。請注意,新對映現在佔據內部對映列表中的第一個位置,.maps。

使用.new_child(),您可以建立一個子上下文,您可以在不更改任何現有對映的情況下更新該子上下文。例如,如果您.new_child()不帶引數呼叫,則它使用一個空字典並將其放在.maps. 在此之後,您可以對新的空對映執行任何更改,使對映的其餘部分保持只讀。

跳過子上下文 .parents

的另一個有趣的功能ChainMap是.parents。此屬性返回一個新ChainMap例項,其中包含底層鏈對映中除第一個之外的所有對映。當您在給定鏈對映中搜尋鍵時,此功能對於跳過第一個對映很有用:

>>> from collections import ChainMap

>>> mom = {"name": "Jane", "age": 31}
>>> dad = {"name": "John", "age": 35}
>>> son = {"name": "Mike", "age":  0}

>>> family = ChainMap(son, mom, dad)
>>> family
ChainMap(
    {'name': 'Mike', 'age': 0},
    {'name': 'Jane', 'age': 31},
    {'name': 'John', 'age': 35}
)

>>> family.parents
ChainMap({'name': 'Jane', 'age': 31}, {'name': 'John', 'age': 35})

在此示例中,您使用.parents跳過包含兒子資料的第一個字典。在某種程度上,.parents是 的逆.new_child()。前者刪除一個字典,而後者在列表的開頭新增一個新字典。在這兩種情況下,您都會獲得一個新的鏈圖。

管理範圍和上下文 ChainMap

可以說,主要用例ChainMap是提供一種有效的方法來管理多個範圍或上下文並處理重複鍵的訪問優先順序。當您有多個儲存重複鍵的字典並且您想定義程式碼訪問它們的順序時,此功能很有用。

在ChainMap文件 中,您將找到一個經典示例,該示例模擬 Python 如何解析不同名稱空間中的變數名稱。

當 Python 查詢名稱時,它會按照相同的順序依次搜尋本地、全域性和內建作用域,直到找到目標名稱。Python 作用域是將名稱對映到物件的字典。

要模擬 Python 的內部查詢鏈,您可以使用鏈對映:

>>> import builtins

>>> # Shadow input with a global name
>>> input = 42

>>> pylookup = ChainMap(locals(), globals(), vars(builtins))

>>> # Retrieve input from the global namespace
>>> pylookup["input"]
42

>>> # Remove input from the global namespace
>>> del globals()["input"]

>>> # Retrieve input from the builtins namespace
>>> pylookup["input"]
<built-in function input>

在此示例中,您首先建立一個名為的全域性變數input,該變數隱藏input()作用builtins域中的內建函式。然後建立pylookup一個鏈對映,其中包含儲存每個 Python 作用域的三個字典。

當您input從檢索時pylookup,您會42從全域性範圍中獲得值。如果您input從globals()字典中刪除鍵並再次訪問它,那麼您input()將從builtins作用域中獲得內建函式,它在 Python 的查詢鏈中具有最低優先順序。

同樣,您可以使用ChainMap來定義和管理重複鍵的鍵查詢順序。這允許您優先訪問所需的重複金鑰例項。

ChainMap在標準庫中跟蹤

的起源ChainMap密切相關的效能問題中ConfigParser,其生活中configparser的標準庫模組。有了ChainMap,核心 Python 開發人員通過優化ConfigParser.get().

您還可以在模組中找到ChainMap作為一部分。此類將字串模板作為引數,並允許您執行PEP 292 中所述的字串替換。輸入字串模板包含您以後可以用實際值替換的嵌入識別符號:Templatestring

>>> import string

>>> greeting = "Hey $name, welcome to $place!"
>>> template = string.Template(greeting)

>>> template.substitute({"name": "Jane", "place": "the World"})
'Hey Jane, welcome to the World!'

當您為字典提供值name並place通過字典提供值時,.substitute()在模板字串中替換它們。此外,.substitute()可以將值作為關鍵字引數 ( **kwargs),這在某些情況下可能會導致名稱衝突:

>>> import string

>>> greeting = "Hey $name, welcome to $place!"
>>> template = string.Template(greeting)

>>> template.substitute(
...     {"name": "Jane", "place": "the World"},
...     place="Real Python"
... )
'Hey Jane, welcome to Real Python!'

在此示例中,.substitute()替換place為您作為關鍵字引數提供的值,而不是輸入字典中的值。如果您深入研究此方法的程式碼,就會發現它用於ChainMap在發生名稱衝突時有效地管理輸入值的優先順序。

這是來自 的原始碼片段.substitute():

# string.py
# Snip...
from collections import ChainMap as _ChainMap

_sentinel_dict = {}

class Template:
    """A string class for supporting $-substitutions."""
    # Snip...

    def substitute(self, mapping=_sentinel_dict, /, **kws):
        if mapping is _sentinel_dict:
            mapping = kws
        elif kws:
            mapping = _ChainMap(kws, mapping)
        # Snip...

在這裡,突出顯示的行發揮了作用。它使用一個鏈對映,該對映將兩個字典kws和mapping, 作為引數。通過放置kws作為第一個引數,該方法設定輸入資料中重複識別符號的優先順序。

將 PythonChainMap付諸行動

到目前為止,您已經學會了如何將ChainMap多個字典合二為一。您還了解了ChainMap本課程與常規詞典的特點和不同之處。的用例ChainMap是相當具體的。它們包括:

  • 單個檢視中有效地對多個字典進行分組
  • 搜尋具有特定優先順序的多個詞典
  • 提供一系列預設值並管理它們的優先順序
  • 提高經常計算字典子集的程式碼的效能

在本節中,您將編寫一些實際示例,這些示例將幫助您更好地瞭解如何使用它ChainMap來解決實際問題。

將多個庫存作為一個訪問

您將編寫程式碼的第一個示例用於ChainMap在單個檢視中高效搜尋多個字典。在這種情況下,您會假設您有一堆具有唯一鍵的獨立字典。

假設您正在經營一家銷售水果和蔬菜的商店。您已經編寫了一個 Python 應用程式來管理您的庫存。該應用程式從資料庫中讀取資料並返回兩個字典,分別包含有關水果和蔬菜價格的資料。您需要一種有效的方法在單個字典中對這些資料進行分組和管理。

經過一些研究,您最終使用ChainMap:

>>> from collections import ChainMap

>>> fruits_prices = {"apple": 0.80, "grape": 0.40, "orange": 0.50}
>>> veggies_prices = {"tomato": 1.20, "pepper": 1.30, "onion": 1.25}
>>> prices = ChainMap(fruits_prices, veggies_prices)

>>> order = {"apple": 4, "tomato": 8, "orange": 4}

>>> for product, units in order.items():
...     price = prices[product]
...     subtotal = units * price
...     print(f"{product:6}: ${price:.2f} × {units} = ${subtotal:.2f}")
...
apple : $0.80 × 4 = $3.20
tomato: $1.20 × 8 = $9.60
orange: $0.50 × 4 = $2.00

在此示例中,您使用 aChainMap建立一個類似字典的物件,該物件將來自fruits_prices和 的資料分組veggies_prices。這允許您訪問底層資料,就像您有效地擁有單個字典一樣。該for迴圈迭代的產品在一個給定的order。然後它計算每種產品型別的小計,並將其列印在您的螢幕上。

您可能會考慮將資料分組到一個新字典中,.update()在迴圈中使用。當您的產品種類有限且庫存較少時,這可能會很好地工作。但是,如果您管理許多不同型別的產品,那麼.update()與ChainMap.

使用ChainMap來解決這類問題也可以幫助你定義不同批次產品的優先順序,讓你管理一個先入/先出(你的庫存FIFO)的方式。

優先考慮命令列應用程式設定

ChainMap在管理應用程式中的預設配置值方面特別有用。如您所知,它的主要功能之一ChainMap是允許您設定關鍵查詢操作的優先順序。這聽起來像是解決管理應用程式配置問題的正確工具。

例如,假設您正在開發一個命令列介面 (CLI)應用程式。該應用程式允許使用者指定用於連線到 Internet 的代理服務。設定優先順序是:

  1. 命令列選項 ( --proxy, -p)
  2. 使用者主目錄中的本地配置檔案
  3. 系統範圍的代理配置

如果使用者在命令列提供代理,則應用程式必須使用該代理。否則,應用程式應使用下一個配置物件中提供的代理,依此類推。這是最常見的用例之一ChainMap。在這種情況下,您可以執行以下操作:

>>> from collections import ChainMap

>>> cmd_proxy = {}  # The user doesn't provide a proxy
>>> local_proxy = {"proxy": "proxy.local.com"}
>>> system_proxy = {"proxy": "proxy.global.com"}

>>> config = ChainMap(cmd_proxy, local_proxy, system_proxy)
>>> config["proxy"]
'proxy.local.com'

ChainMap允許您為應用程式的代理配置定義適當的優先順序。鍵查詢搜尋cmd_proxy,然後local_proxy,最後system_proxy,返回手頭鍵的第一個例項。在示例中,使用者沒有在命令列提供代理,因此應用程式從 中獲取代理local_proxy,這是列表中的下一個設定提供程式。

管理預設引數值

的另一個用例ChainMap是管理方法和函式中的預設引數值。假設您正在編寫一個應用程式來管理有關貴公司員工的資料。您有以下類,它代表一個通用使用者:

class User:
    def __init__(self, name, user_id, role):
        self.name = name
        self.user_id = user_id
        self.role = role

    # Snip...

在某些時候,您需要新增一項功能,允許員工訪問CRM系統的不同元件。您的第一個想法是修改User以新增新功能。但是,這可能會使類過於複雜,因此您決定建立一個子類CRMUser,以提供所需的功能。

該類將使用者name和 CRMcomponent作為引數。它也需要一些**kwargs。您希望以CRMUser一種允許您為基類的初始值設定項提供合理的預設值的方式實現,而不會失去**kwargs.

以下是您使用以下方法解決問題的方法ChainMap:

from collections import ChainMap

class CRMUser(User):
    def __init__(self, name, component, **kwargs):
        defaults = {"user_id": next(component.user_id), "role": "read"}
        super().__init__(name, **ChainMap(kwargs, defaults))

在此程式碼示例中,您將建立User. 在類初始化,你拿name,component以及**kwargs作為引數。然後,您建立一個本地字典,其中user_id和具有預設值role。然後.__init__()使用super(). 在此呼叫中,您name直接傳遞給父級的初始值設定項,並使用鏈對映為其餘引數提供預設值。

請注意,該ChainMap物件將kwargs然後defaults作為引數。此順序可確保在例項化類時手動提供的引數 ( kwargs) 優先於defaults值。

結論

Python的ChainMap從collections模組提供用於管理多個詞典作為單個的有效工具。當您有多個字典表示不同的範圍或上下文並且需要設定對底層資料的訪問優先順序時,這個類很方便。

ChainMap將多個字典和對映組合在一個可更新的檢視中,該檢視的工作方式與字典非常相似。您可以使用ChainMap物件在 Python 中高效地處理多個字典、定義關鍵查詢優先順序以及管理多個上下文。

在本教程中,您學習瞭如何:

  • 在 Python 程式中建立ChainMap例項
  • 探索差異之間ChainMap和dict
  • 使用多個字典作為一個管理ChainMap
  • 設定關鍵查詢操作的優先順序ChainMap

在本教程中,您還編寫了一些實際示例,幫助您更好地理解何時以及如何ChainMap在 Python 程式碼中使用。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章