183

有没有办法让 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? ,这是如何做一个两级默认字典。

我可能最终会使用模式,但是当我意识到我不知道如何做到这一点时,它引起了我的兴趣。

4

10 回答 10

243

这里的其他答案告诉您如何创建一个defaultdict包含 "infinitely many"的内容defaultdict,但它们无法解决我认为您最初的需求,即简单地拥有一个两深度的 defaultdict。

您可能一直在寻找:

defaultdict(lambda: defaultdict(dict))

您可能更喜欢此构造的原因是:

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

对于任意数量的级别:

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)
于 2013-10-04T19:33:55.067 回答
70

这样做有一个绝妙的技巧:

tree = lambda: defaultdict(tree)

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

于 2013-10-04T19:34:18.273 回答
23

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

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

然后你可以xx = tree().


对于该版本,我们可以使用函数闭包作用域来保护数据结构免受名称反弹def时现有实例停止工作的缺陷。tree它看起来像这样:

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()
于 2013-10-04T20:01:29.873 回答
17

我还会提出更多 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': {}}
于 2019-05-28T09:12:50.420 回答
1

我在这里基于安德鲁的回答。如果您希望将数据从 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

于 2020-06-17T21:02:24.680 回答
0

@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)
于 2020-10-15T03:52:52.087 回答
0

但是,根据 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))
于 2021-12-22T10:07:09.060 回答
0

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

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, {})
于 2020-04-10T03:15:11.787 回答
0

这是用于任意嵌套深度的任意基本默认字典的函数。

(来自Can't 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
于 2021-07-15T16:35:02.317 回答