“Python 3.15 的 frozendict 和 dict、MappingProxyType 到底差在哪、值不值得换” 是新内置类型最该先搞清楚的问题。这篇在本机用 3.14.9 和 3.15.0 并排跑,逐项试它的不可变性、可哈希性、和视图的区别,并量了内存和速度。
Python 3.14(退出码 1):
Traceback (most recent call last):
File "<tmp>/s1.py", line 3, in <module>
fd = frozendict(a=1, b=2)
^^^^^^^^^^
NameError: name 'frozendict' is not defined. Did you mean: 'frozenset'?
Python 3.15(退出码 0):
repr: frozendict({'a': 1, 'b': 2})
type: frozendict | __mro__: ['frozendict', 'object']
isinstance(fd, dict) = False | isinstance(fd, abc.Mapping) = True | isinstance(fd, abc.MutableMapping) = False
空的: frozendict() | 从键值对: frozendict({'x': 1}) | 合并关键字: frozendict({'x': 1, 'y': 2})
fd == {"a": 1, "b": 2} -> True | 顺序不同 -> True
frozendict[str, int] -> frozendict[str, int]3.14 里 NameError;3.15 里是内置类型,不是 dict 的子类,是 Mapping,不是 MutableMapping。
Python 3.15(退出码 0):
fd['c'] = 3 -> TypeError: 'frozendict' object does not support item assignment
del fd['a'] -> TypeError: 'frozendict' object does not support item deletion
fd.update(c=3) -> AttributeError: 'frozendict' object has no attribute 'update'
fd.pop('a') -> AttributeError: 'frozendict' object has no attribute 'pop'
fd.clear() -> AttributeError: 'frozendict' object has no attribute 'clear'
fd.setdefault('z', 1) -> AttributeError: 'frozendict' object has no attribute 'setdefault'
fd.popitem() -> AttributeError: 'frozendict' object has no attribute 'popitem'
dict 有而 frozendict 没有的方法: ['clear', 'pop', 'popitem', 'setdefault', 'update']
frozendict 的公开方法: ['copy', 'fromkeys', 'get', 'items', 'keys', 'values']
fd | {"b": 9, "c": 3} -> frozendict({'a': 1, 'b': 9, 'c': 3})
{"b": 9} | fd -> {'b': 2, 'a': 1} (左边是 dict,结果还是 dict)
fd |= {"x": 1} 之后:原来的 fd 仍是 frozendict({'a': 1, 'b': 2}) ,g 是新对象 frozendict({'a': 1, 'b': 2, 'x': 1})赋值和删除是 TypeError;update、pop、clear、setdefault、popitem 不存在;| 返回新的 frozendict。
Python 3.15(退出码 0):
hash 相等(键顺序不同): True
当 dict 的键: {frozendict({'a': 1, 'b': 2}): 'v'} | 放进 set: {frozendict({'a': 1, 'b': 2})}
值里有 list: TypeError: unhashable type: 'list'
lru_cache 传 frozendict: 2 CacheInfo(hits=0, misses=1, maxsize=128, currsize=1)
lru_cache 传 dict: TypeError: unhashable type: 'dict'
dataclass 字段默认值写 {}: ValueError: mutable default <class 'dict'> for field x is not allowed: use default_factory
dataclass 字段默认值写 frozendict(): D(x=frozendict()) | 两个实例共享同一个默认对象: True能当键、放进 set、传给 lru_cache、当 dataclass 默认值;值必须都可哈希。
Python 3.15(退出码 0):
底层 dict 加了 b 之后:MappingProxyType 看到 {'a': 1, 'b': 2} | frozendict 看到 {'a': 1}
hash(MappingProxyType): TypeError: unhashable type: 'dict'
hash(frozendict) 能算: True
frozendict == MappingProxyType: True
copy.copy(fd) is fd: True | copy.deepcopy(fd) is fd: False | deepcopy 后相等: True
json.dumps(fd): {"a": 1}
pickle 往返: frozendict({'a': 1, 'b': [1, 2]})
match 映射模式能用,rest = {}
fd.keys() & {"a"} = {'a'} | 顺序: ['z', 'a']MappingProxyType 随底层变化且不可哈希;frozendict 是独立副本。
10 个键 1000 个键 100000 个键 占内存 dict (字节) 272 26032 3844864 占内存 frozendict (字节) 280 26040 3844872 构造 dict(d) (μs) 0.1 2.2 369.5 构造 frozendict(d) (μs) 0.1 2.2 360.0 读取 d[k] (μs) 0.028 0.027 0.027 读取 fd[k] (μs) 0.029 0.027 0.028 读取 mp[k] (μs) 0.032 0.030 0.031 遍历 d (μs) 0.2 11.2 860.7 遍历 fd (μs) 0.2 11.4 846.5 构造 + 第一次 hash(fd) (μs) 0.2 7.2 838.4 同一个 fd 再次 hash (μs) 0.033 0.033 0.032 tuple(sorted(d.items())) (μs) 0.4 43.7 5269.0
内存和读取与 dict 基本一样;哈希值缓存,第二次起几乎免费。