将 `defaultdict` 公开为常规 `dict`

Posted

技术标签:

【中文标题】将 `defaultdict` 公开为常规 `dict`【英文标题】:Exposing `defaultdict` as a regular `dict` 【发布时间】:2012-11-08 01:49:41 【问题描述】:

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

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

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

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

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

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

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

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

【问题讨论】:

“重写”只会使用dict.setdefault 方法...没什么大不了的 @JBernardo 您在谈论选项 4 吗?我对defaultdict 的了解是它会覆盖__getitem__ 以在需要时添加元素。也许它使用setdefault 方法来做到这一点,也许它直接实现相同的逻辑而无需调用setdefault。如果不依赖实现细节,我不能假设任何事情,可以吗? 他指的是您的选项#5。只需在代码中使用您的 data.setdefault() 来替换 defaultdict 我认为你应该可以通过在defaultdict 上调用dict 来解决它 @inspectorG4dget 数据结构的大小超过 1 GB,因此复制所有数据(如果我调用 dict 会发生这种情况)太贵了。 【参考方案1】:

完成填充默认字典后,您可以简单地从中创建一个常规字典:

my_dict = dict(my_default_dict)

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

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

【讨论】:

【参考方案2】:

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'
>>> 

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

【讨论】:

谁知道我提出的解决方案已经作为defaultdict的功能实现了?很棒的发现。 (+1) 哇,这太完美了。我希望将 default_factory 更改为现有的 defaultdict 对象是安全的(我不明白为什么不这样做)。 @max -- 文档明确指出default_factory 是一个可写 属性,所以它应该是安全的。 @max:使用源:defdictobjectdefdict_members(名称、类型、偏移量、标志、文档;flags==0 表示它是可写的)、defdict_missing【参考方案3】:

您可以创建一个包含对您的 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

【讨论】:

会不会超级慢,因为它使用纯python进行关键方法? 对于getitem方法?不确定与 defaultdict 相比的性能开销 无论哪种方式,我认为尼尔的解决方案最适合您的问题

以上是关于将 `defaultdict` 公开为常规 `dict`的主要内容,如果未能解决你的问题,请参考以下文章

Python进阶可能是全网最详细的defaultdict讲解

Python进阶可能是全网最详细的defaultdict讲解

如何将DI存储库转换为Type-class?

不理解这个带有 defaultdict 的 lambda 表达式

将带有字典列表的 defaultdict(list) 字典转换为 csv 的最佳方法

将现有的 defaultdict 输出为适合耀斑树状图的 JSON 格式?