有没有办法让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。

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


当前回答

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

(cross post from 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

其他回答

然而,根据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))

对于任意数量的层:

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。

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

(cross post from 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

类似于BrenBarn的解决方案,但不包含两次变量树的名称,所以即使在修改变量字典后也能工作:

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

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


对于def版本,我们可以使用函数闭包作用域来保护数据结构,避免在树名为“反弹”时现有实例停止工作的缺陷。它是这样的:

from collections import defaultdict

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