36

我正在使用defaultdict(set)在非常大的数据结构中填充内部映射。填充后,整个结构(包括映射)将暴露给客户端代码。那时,我不希望任何人修改映射。

没有人故意这样做。但有时,客户端代码可能会意外引用不存在的元素。那时,一个普通的字典会产生KeyError,但由于映射是defaultdict,它只是在该键处创建一个新元素(一个空集)。这很难捕捉,因为一切都在悄无声息地发生。但我需要确保不会发生这种情况(语义实际上不会中断,但映射会增长到巨大的大小)。

我该怎么办?我可以看到这些选择:

  1. 在当前和未来的客户端代码中查找对映射执行字典查找的所有实例,并将其转换为mapping.get(k, {})。这太可怕了。

  2. defaultdict在数据结构完全初始化后“冻结” ,将其转换为dict. (我知道它并没有真正冻结,但我相信客户端代码实际上不会编写mapping[k] = v。)不优雅,并且性能受到很大影响。

  3. 包装defaultdict成一个dict界面。有什么优雅的方法来做到这一点?恐怕性能损失可能很大(这种查找在紧密循环中大量使用)。

  4. 子类defaultdict化并添加一个“关闭”所有defaultdict功能的方法,让它的行为就好像它是一个常规的dict. 它是上述 3 的变体,但我不确定它是否更快。如果不依赖实现细节,我不知道它是否可行。

  5. 在数据结构中使用正则dict,重写那里的所有代码以首先检查元素是否在字典中,如果不在则添加。不好。

4

3 回答 3

57

defaultdict文档说default_factory

如果 default_factory 属性为 None,则会引发 KeyError 异常,并将键作为参数。

如果您只是将 defaultdict 的 default_factory 设置为None怎么办?例如,

>>> d = defaultdict(int)
>>> d['a'] += 1
>>> d
defaultdict(<type 'int'>, {'a': 1})
>>> d.default_factory = None
>>> d['b'] += 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'b'
>>> 

不确定这是否是最好的方法,但似乎有效。

于 2012-11-20T02:29:33.173 回答
3

完成填充 defaultdict 后,您可以简单地从中创建一个常规 dict:

my_dict = dict(my_default_dict)

可以选择使用typing.Final类型注释。

如果默认字典是递归默认字典,请参阅使用递归解决方案的此答案。

于 2017-09-24T19:35:59.237 回答
0

您可以创建一个包含对您的 dict 的引用并防止setitem ()的类

from collections import Mapping

class MyDict(Mapping):
    def __init__(self, d):
        self.d = d;

    def __getitem__(self, k):
        return self.d[k]

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

    def __setitem__(self, k, v):
        if k not in self.d.keys():
            raise KeyError
        else:
            self.d[k] = v
于 2012-11-20T02:48:27.687 回答