什么是“冻结字典”?

229 投票
14 回答
146233 浏览
提问于 2025-04-15 21:58
  • 一个“冻结集合”就是一个 frozenset。
  • 一个“冻结列表”可以是一个元组。
  • 那“冻结字典”是什么呢?就是一个不可变的、可哈希的字典。

我想它可能像 collections.namedtuple,但那更像是一个“半冻结”的字典(也就是键是冻结的字典)。对吧?

一个“冻结字典”应该是一个冻结的字典,它应该有 keysvaluesget 等等,并且支持 infor 等等。

更新:
* 这里有链接: https://www.python.org/dev/peps/pep-0603

14 个回答

27

假设字典里的键和值都是不可改变的(比如字符串),那么:

>>> d
{'forever': 'atones', 'minks': 'cards', 'overhands': 'warranted', 
 'hardhearted': 'tartly', 'gradations': 'snorkeled'}
>>> t = tuple((k, d[k]) for k in sorted(d.keys()))
>>> hash(t)
1524953596
103

有趣的是,虽然我们有不常用的 frozenset,但仍然没有“冻结”的映射类型。这个想法在 PEP 416 -- 添加一个 frozendict 内置类型 中被拒绝了。这个想法可能会在以后的 Python 版本中重新考虑,参见 PEP 603 -- 向集合添加一个 frozenmap 类型

所以在 Python 2 中的解决方案是:

def foo(config={'a': 1}):
    ...

看起来还是比较常见的做法:

def foo(config=None):
    if config is None:
        config = {'a': 1}  # default config
    ...

在 Python 3 中,你可以选择 这个

from types import MappingProxyType

default_config = {'a': 1}
DEFAULTS = MappingProxyType(default_config)

def foo(config=DEFAULTS):
    ...

现在默认配置 可以 动态更新,但在你希望它保持不变的地方,仍然可以通过传递代理来保持不变。

所以对 default_config 的更改会按预期更新 DEFAULTS,但你不能直接写入映射代理对象本身。

诚然,这并不完全等同于“不可变的、可哈希的字典”,但在某些使用场景下,它可能是 frozendict 的一个不错替代品。

151

Python没有内置的不可变字典类型(frozendict)。其实,这种类型并不常用(不过比起不可变集合(frozenset),可能还是会用得更多)。

人们通常想要这种类型的原因是为了在处理函数调用时能记住那些参数不确定的函数。最常见的解决办法是存储一个可以哈希的字典等价物(前提是字典的值是可以哈希的),通常用的方式是 tuple(sorted(kwargs.items()))

这个方法依赖于排序的结果不会太离谱。Python不能保证排序的结果总是合理的。(不过它也不能保证其他很多事情,所以不用太担心。)


你可以很容易地创建一个类似字典的包装器。它可能看起来像这样:

import collections

class FrozenDict(collections.Mapping):
    """Don't forget the docstrings!!"""
    
    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)
        self._hash = None

    def __iter__(self):
        return iter(self._d)

    def __len__(self):
        return len(self._d)

    def __getitem__(self, key):
        return self._d[key]

    def __hash__(self):
        # It would have been simpler and maybe more obvious to 
        # use hash(tuple(sorted(self._d.iteritems()))) from this discussion
        # so far, but this solution is O(n). I don't know what kind of 
        # n we are going to run into, but sometimes it's hard to resist the 
        # urge to optimize when it will gain improved algorithmic performance.
        if self._hash is None:
            hash_ = 0
            for pair in self.items():
                hash_ ^= hash(pair)
            self._hash = hash_
        return self._hash

这个方法应该能很好地工作:

>>> x = FrozenDict(a=1, b=2)
>>> y = FrozenDict(a=1, b=2)
>>> x is y
False
>>> x == y
True
>>> x == {'a': 1, 'b': 2}
True
>>> d = {x: 'foo'}
>>> d[y]
'foo'

撰写回答