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