PEP 416 – 新增一個內建的 frozendict 型別
- 作者:
- Victor Stinner <vstinner at python.org>
- 狀態:
- 已拒絕
- 型別:
- 標準跟蹤
- 建立日期:
- 2012年2月29日
- Python 版本:
- 3.3
拒絕通知
我拒絕此 PEP。原因有(不詳盡)
- 根據 Raymond Hettinger 的說法,frozendict 的使用率很低。那些使用它的人傾向於將其僅用作提示,例如宣告全域性或類級別的“常量”:它們並非真正不可變,因為任何人仍然可以為該名稱賦值。
- 存在避免可變預設值的現有習慣用法。
- 在 PyPy 中使用 frozendict 最佳化程式碼的潛力是不確定的;許多其他事情需要先改變。編譯時查詢一般也是如此。
- 多個執行緒可以透過約定不同意修改共享的字典,沒有太大的強制需求。多個程序無法共享字典。
- 許多人反對用 Python 編寫安全沙箱,即使範圍有限,因為永遠無法證明沙箱實際上是安全的。因此,我們近期不會將其新增到標準庫中,因此這種用例超出了 PEP 的範圍。
另一方面,將現有的只讀字典代理暴露為內建型別對我來說聽起來不錯。(它需要被修改為允許呼叫建構函式。)GvR。
更新(2012-04-15):Python 3.3 的 types 模組中添加了一個新的 MappingProxyType 型別。
摘要
新增一個新的內建 frozendict 型別。
基本原理
frozendict 是一個只讀對映:不能新增或刪除鍵,並且鍵始終對映到相同的值。但是,frozendict 的值可能不可雜湊。frozendict 僅當所有值都可雜湊時才可雜湊。
用例
- 不可變全域性變數,如預設配置。
- 函式引數的預設值。避免可變預設引數的問題。
- 實現快取:frozendict 可用於儲存函式關鍵字。frozendict 可用作對映的鍵或集合的成員。
- frozendict 避免了在 frozendict 被多個執行緒或程序共享時需要鎖,特別是可雜湊的 frozendict。它還有助於禁止協程(生成器 + greenlet)修改全域性狀態。
- 由於對映是隻讀的,因此可以在編譯時而不是執行時進行 frozendict 查詢。frozendict 可用於代替預處理器來在編譯時刪除條件程式碼,例如特定於除錯構建的程式碼。
- frozendict 有助於為安全模組實現只讀物件代理。例如,可以使用 frozendict 型別作為 `__builtins__` 對映或 `type.__dict__`。這是可能的,因為 frozendict 與 PyDict C API 相容。
- frozendict 在某些情況下可以避免使用只讀代理。frozendict 比代理快,因為在 frozendict 中獲取項是一個快速查詢,而代理需要函式呼叫。
限制
- frozendict 必須實現 Mapping 抽象基類
- frozendict 的鍵和值可以是無序的
- 僅當所有鍵和值都可雜湊時,frozendict 才可雜湊
- frozendict 的雜湊不依賴於項的建立順序
實施
- 新增一個基於 PyDictObject 的 PyFrozenDictObject 結構,並帶有一個額外的“Py_hash_t hash;”欄位
- frozendict.__hash__() 使用 hash(frozenset(self.items())) 實現,並將結果快取到其私有的 hash 屬性中
- 將 frozendict 註冊為 collections.abc.Mapping
- frozendict 可與 PyDict_GetItem() 一起使用,但 PyDict_SetItem() 和 PyDict_DelItem() 會引發 TypeError
方案:可雜湊的字典
為了確保 frozendict 可雜湊,可以在建立 frozendict 之前檢查值
import itertools
def hashabledict(*args, **kw):
# ensure that all values are hashable
for key, value in itertools.chain(args, kw.items()):
if isinstance(value, (int, str, bytes, float, frozenset, complex)):
# avoid the compute the hash (which may be slow) for builtin
# types known to be hashable for any value
continue
hash(value)
# don't check the key: frozendict already checks the key
return frozendict.__new__(cls, *args, **kw)
反對意見
namedtuple 可能符合 frozendict 的要求。
namedtuple 不是對映,它不實現 Mapping 抽象基類。
frozendict 可以使用描述符用 Python 實現”並且“frozendict 只需要在實踐中是常量。”
如果 frozendict 用於加固 Python(出於安全目的),則必須用 C 實現。用 C 實現的型別也更快。
PEP 351 被拒絕了。
PEP 351 嘗試凍結一個物件,因此可能會將可變物件轉換為不可變物件(使用不同的型別)。frozendict 不會轉換任何東西:如果值不可雜湊,hash(frozendict) 會引發 TypeError。凍結物件不是此 PEP 的目的。
替代方案:dictproxy
Python 有一個內建的 dictproxy 型別,由 type.__dict__ getter 描述符使用。此型別不是公開的。dictproxy 是字典的只讀檢視,但它不是隻讀對映。如果修改了字典,dictproxy 也會被修改。
可以使用 ctypes 和 Python C API 來使用 dictproxy,例如,請參閱 Ikkei Shimomura 的 透過 ctypes.pythonapi 和 type() 建立 dictproxy 物件(Python 菜譜 576540)。該菜譜包含一個測試,用於檢查 dictproxy 是否“可變”(修改連結到 dictproxy 的字典)。
然而,dictproxy 在某些情況下可能有用,當其可變屬性不是問題時,可以避免複製字典。
現有實現
白名單方法。
- Aristotelis Mikropoulos 的 實現不可變字典(Python 菜譜 498072)。與 frozendict 類似,只是它不是真正只讀的:可以訪問其私有的內部字典。它不實現 `__hash__` 並且存在實現問題:可以再次呼叫 `__init__()` 來修改對映。
- PyWebmail 包含一個 ImmutableDict 型別:webmail.utils.ImmutableDict。僅當鍵和值都可雜湊時,它才可雜湊。它不是真正只讀的:其內部字典是公共屬性。
- remember 專案:remember.dicts.FrozenDict。它用於實現快取:FrozenDict 用於儲存函式回撥。FrozenDict 可能可雜湊。它有一個額外的 `supply_dict()` 類方法,用於從字典建立 FrozenDict 而無需複製字典:將字典儲存為內部字典。實現問題:可以呼叫 `__init__()` 來修改對映,並且雜湊可能因項的建立順序而異。對映不是真正只讀的:內部字典可以在 Python 中訪問。
黑名單方法:繼承自 dict 並重寫寫入方法以引發異常。它不是真正只讀的:仍然可以對這種“凍結字典”呼叫 dict 方法來修改它。
- brownie:brownie.datastructures.ImmutableDict。僅當鍵和值都可雜湊時,它才可雜湊。werkzeug 專案具有相同的程式碼:werkzeug.datastructures.ImmutableDict。ImmutableDict 用於全域性常量(配置選項)。Flask 專案使用 werkzeug 的 ImmutableDict 作為其預設配置。
- SQLAlchemy 專案:sqlalchemy.util.immutabledict。它不可雜湊,並且有一個額外的 `union()` 方法。immutabledict 用於需要對映的某些函式的引數的預設值。例如:`mapper_args=immutabledict()` 在 SqlSoup.map() 中。
- Oren Tirosh 的 凍結字典(Python 菜譜 414283)。僅當鍵和值都可雜湊時,它才可雜湊。包含在以下專案中
- lingospot:frozendict/frozendict.py
- factor-graphics:python/fglib/util_ext_frozendict.py 中的 frozendict 型別
- George Sakkis 編寫的 gsakkis-utils 專案包含一個 frozendict 型別:datastructs.frozendict
- characters:scripts/python/frozendict.py。它可雜湊。`__init__()` 將 `__init__` 設定為 None。
- 舊 NLTK (1.x):nltk.util.frozendict。鍵和值必須可雜湊。可以呼叫 `__init__()` 兩次來修改對映。frozendict 用於“凍結”一個物件。
可雜湊字典:繼承自 dict 並僅新增 `__hash__` 方法。
- pypy.rpython.lltypesystem.lltype.frozendict。它可雜湊但並不禁止修改對映。
- factor-graphics:python/fglib/util_ext_frozendict.py 中的 hashabledict 型別
連結
- Issue #14162: PEP 416: 新增一個內建的 frozendict 型別
- PEP 412:鍵共享字典(issue #13903)
- PEP 351:凍結協議
- 關於不可變字典的論證;以及對 PEP 351 的根本誤解
- Ikkei Shimomura 的 透過 ctypes.pythonapi 和 type() 建立 dictproxy 物件(Python 菜譜 576540)。
- 使用 C 擴充套件實現只讀物件代理的 Python 安全模組
版權
本文件已置於公共領域。
來源:https://github.com/python/peps/blob/main/peps/pep-0416.rst