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):一个新的MappingProxyType
类型已添加到Python 3.3的types模块中。
摘要
添加一个新的frozendict内置类型。
基本原理
frozendict是一个只读映射:无法添加或删除键,并且键始终映射到相同的值。但是,frozendict的值可以不可哈希。frozendict 可哈希当且仅当所有值都可哈希。
用例
- 像默认配置这样的不可变全局变量。
- 函数参数的默认值。避免可变默认参数的问题。
- 实现缓存:frozendict可用于存储函数关键字。frozendict可用于映射的键或集合的成员。
- 当frozendict由多个线程或进程共享时,frozendict避免了锁的需求,尤其是可哈希的frozendict。它还有助于禁止协程(生成器+greenlets)修改全局状态。
- 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可以使用描述符”和“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在某些情况下可能很有用,在这些情况下,其可变属性不是问题,可以避免复制字典。
现有实现
白名单方法。
- 实现不可变字典(Python配方498072)作者:Aristotelis Mikropoulos。类似于frozendict,只是它不是真正只读的:可以访问此私有内部字典。它没有实现__hash__,并且存在实现问题:可以再次调用__init__()来修改映射。
- PyWebmail包含一个ImmutableDict类型:webmail.utils.ImmutableDict。如果键和值可哈希,则它可哈希。它不是真正只读的:其内部字典是公共属性。
- remember项目:remember.dicts.FrozenDict。它用于实现缓存:FrozenDict用于存储函数回调。FrozenDict可能是可哈希的。它有一个额外的supply_dict()类方法,用于从字典创建FrozenDict而不复制字典:将字典存储为内部字典。实现问题:可以调用__init__()来修改映射,并且哈希可能因项创建顺序而异。映射不是真正只读的:内部字典可在Python中访问。
黑名单方法:继承自dict并覆盖写入方法以引发异常。它不是真正只读的:仍然可以在此类“冻结字典”上调用字典方法来修改它。
- brownie:brownie.datastructures.ImmutableDict。如果键和值可哈希,则它可哈希。werkzeug项目具有相同的代码:werkzeug.datastructures.ImmutableDict。ImmutableDict用于全局常量(配置选项)。Flask项目使用werkzeug的ImmutableDict作为其默认配置。
- SQLAlchemy项目:sqlalchemy.util.immutabledict。它不可哈希,并且有一个额外的方法:union()。immutabledict用于某些期望映射的函数的参数的默认值。示例:SqlSoup.map()中的mapper_args=immutabledict()。
- 冻结字典(Python配方414283)作者:Oren Tirosh。如果键和值可哈希,则它可哈希。包含在以下项目中
- 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类型
链接
- 问题#14162:PEP 416:添加内置frozendict类型
- PEP 412:键共享字典(问题#13903)
- PEP 351:冻结协议
- 不可变字典的案例;以及对PEP 351的核心误解
- 通过ctypes.pythonapi和type()创建dictproxy对象(Python配方576540)作者:Ikkei Shimomura。
- 使用C扩展实现只读对象代理的Python安全模块
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0416.rst