defaultdict 嵌套 defaultdict

有没有办法让defaultdict也成为defaultdict的默认值?(即无限级递归defaultdict?)

我希望能够做到:

x = defaultdict(...stuff...)
x[0][1][0]
{}

因此,我可以执行x = defaultdict(defaultdict),但这只是第二个级别:

x[0]
{}
x[0][0]
KeyError: 0

有些食谱可以做到这一点。但是仅仅使用普通的defaultdict参数就可以做到吗?

注意,这是在询问如何执行无限级递归defaultdict,因此它与Python: defaultdict of defaultdict?< / >不同,后者是如何执行两级defaultdict。

我可能最终只使用模式,但当我意识到我不知道如何做到这一点时,它让我感兴趣。

77606 次浏览

对于任意数量的层:

def rec_dd():
return defaultdict(rec_dd)


>>> x = rec_dd()
>>> x['a']['b']['c']['d']
defaultdict(<function rec_dd at 0x7f0dcef81500>, {})
>>> print json.dumps(x)
{"a": {"b": {"c": {"d": {}}}}}

当然你也可以用lambda来做这个,但是我发现lambda可读性较差。在任何情况下,它看起来是这样的:

rec_dd = lambda: defaultdict(rec_dd)

这里有一个妙招:

tree = lambda: defaultdict(tree)

然后你可以用x = tree()创建你的x

类似于BrenBarn的解决方案,但没有两次包含变量tree的名称,因此即使在变量字典更改之后,它也可以工作:

tree = (lambda f: f(f))(lambda a: (lambda: defaultdict(a(a))))

然后你可以用x = tree()创建每个新的x


对于def版本,我们可以使用函数闭包作用域来保护数据结构不受缺陷的影响,即如果tree名称为“反弹”,现有实例将停止工作。它是这样的:

from collections import defaultdict


def tree():
def the_tree():
return defaultdict(the_tree)
return the_tree()

这里的其他答案告诉你如何创建一个包含“无限多个”defaultdictdefaultdict,但它们未能解决我认为可能是你最初的需求,即简单地拥有一个双深度defaultdict。

你可能一直在寻找:

defaultdict(lambda: defaultdict(dict))

你可能更喜欢这个结构的原因是:

  • 它比递归解决方案更明确,因此读者可能更容易理解。
  • 这使得defaultdict的“叶子”可以是字典以外的东西,例如:defaultdict(lambda: defaultdict(list))defaultdict(lambda: defaultdict(set))

我还会建议更多oop风格的实现,它支持无限嵌套以及正确格式化的repr

class NestedDefaultDict(defaultdict):
def __init__(self, *args, **kwargs):
super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)


def __repr__(self):
return repr(dict(self))

用法:

my_dict = NestedDefaultDict()
my_dict['a']['b'] = 1
my_dict['a']['c']['d'] = 2
my_dict['b']


print(my_dict)  # {'a': {'b': 1, 'c': {'d': 2}}, 'b': {}}

下面是一个递归函数,用于将递归默认字典转换为普通字典

def defdict_to_dict(defdict, finaldict):
# pass in an empty dict for finaldict
for k, v in defdict.items():
if isinstance(v, defaultdict):
# new level created and that is the new value
finaldict[k] = defdict_to_dict(v, {})
else:
finaldict[k] = v
return finaldict


defdict_to_dict(my_rec_default_dict, {})
我基于Andrew的回答在这里。 如果你想从json或现有的dict中加载数据到nester defaultdict中,请看这个例子:

def nested_defaultdict(existing=None, **kwargs):
if existing is None:
existing = {}
if not isinstance(existing, dict):
return existing
existing = {key: nested_defaultdict(val) for key, val in existing.items()}
return defaultdict(nested_defaultdict, existing, **kwargs)

https://gist.github.com/nucklehead/2d29628bb49115f3c30e78c071207775

@nucklehead的响应也可以扩展到处理JSON中的数组:

def nested_dict(existing=None, **kwargs):
if existing is None:
existing = defaultdict()
if isinstance(existing, list):
existing = [nested_dict(val) for val in existing]
if not isinstance(existing, dict):
return existing
existing = {key: nested_dict(val) for key, val in existing.items()}
return defaultdict(nested_dict, existing, **kwargs)

下面是一个函数,用于嵌套的任意深度的任意基defaultdict。

(从不能pickle defaultdict交叉发布)

def wrap_defaultdict(instance, times=1):
"""Wrap an instance an arbitrary number of `times` to create nested defaultdict.
    

Parameters
----------
instance - list, dict, int, collections.Counter
times - the number of nested keys above `instance`; if `times=3` dd[one][two][three] = instance
    

Notes
-----
using `x.copy` allows pickling (loading to ipyparallel cluster or pkldump)
- thanks https://stackoverflow.com/questions/16439301/cant-pickle-defaultdict
"""
from collections import defaultdict


def _dd(x):
return defaultdict(x.copy)


dd = defaultdict(instance)
for i in range(times-1):
dd = _dd(dd)


return dd

然而,根据Chris W的回答,为了解决类型注释问题,您可以将其作为定义详细类型的工厂函数。例如,当我研究这个问题时,这是我问题的最终解决方案:

def frequency_map_factory() -> dict[str, dict[str, int]]:
"""
Provides a recorder of: per X:str, frequency of Y:str occurrences.
"""
return defaultdict(lambda: defaultdict(int))