如何在 Python 中建立一個不可變的字典 - Adam Johnson

banq發表於2022-01-07

Python 的內建集合型別具有可變和不可變兩種風格:

  • 可變版本:list 不可變版本:tuple
  • 可變版本:set  不可變版本:frozenset
  • 可變版本:dict 不可變版本:MappingProxyType

PEP 416早在 2012 年就為 Python 3.3提出了一種型別frozendict。 PEP 被拒絕,原因有很多。推理包括幾個關於不可變 dict 的效用的問題,在將它們新增到程式碼之前值得檢查一下。

但是 PEP確實為我們提供了一個模擬不可變dicts:的工具types.MappingProxyType。此型別是 adict或其他對映的只讀代理。Python 在內部將這種型別用於重要的詞典,這就是為什麼您不能隨意修補內建型別的原因。

Python 3.3 中唯一的變化是為使用者程式碼公開這種型別。

要建立一個“不可變”的字典,MappingProxyType從字典中建立一個,而不保留對底層字典的任何引用:

from types import MappingProxyType

power_levels = MappingProxyType(
    {
        "Kevin": 9001,
        "Benny": 8000,
    }
)
讀取:
In [1]: power_levels["Kevin"]
Out[1]: 9001

In [2]: power_levels["Benny"]
Out[2]: 8000

In [3]: list(power_levels.keys())
Out[3]: ['Kevin', 'Benny']

但是,任何更改值的嘗試都將導致TypeError:

In [4]: power_levels["Benny"] = 9200
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-39fccb3e4f76> in <module>
----> 1 power_levels["Benny"] = 9200

TypeError: 'mappingproxy' object does not support item assignment

要使用更改建立對映代理的副本,您可以使用 Python 3.9 的dict 合併運算子。將對映代理與新字典合併,並將結果傳遞給MappingProxyType

In [10]: benny_better = MappingProxyType(power_levels | {"Benny": 9200})

In [11]: benny_better
Out[11]: mappingproxy({'Kevin': 9001, 'Benny': 9200})

對於更復雜的修改,您可以將對映代理複製到新的dict進行更改,然後將結果轉換為對映代理:

In [12]: new_world = power_levels | {}

In [13]: del new_world["Benny"]

In [14]: del new_world["Kevin"]

In [15]: new_world["Bock"] = 100

In [16]: new_world = MappingProxyType(new_world)

In [17]: new_world
Out[17]: mappingproxy({'Bock': 100})

 

有幾個提供不可變資料結構的第三方包,例如immutablespyrsistent。這些 API 稍微好一些,但會帶來不同的權衡,例如效能和維護狀態。如果MappingProxyType不適合您,您可能想研究它們,但我鼓勵儘可能多地使用標準庫。

相關文章