将 `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:使用源:defdictobject
、defdict_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讲解
不理解这个带有 defaultdict 的 lambda 表达式