具有可变深度的多级defaultdict?


70

我有很多类似的清单:

[A][B1][C1]=1
[A][B1][C2]=2
[A][B2]=3
[D][E][F][G]=4

我想建立一个多层次的字典,像:

A
--B1
-----C1=1
-----C2=1
--B2=3
D
--E
----F
------G=4

我知道,如果我用递归defaultdict我可以写table[A][B1][C1]=1table[A][B2]=2但如果我硬编码的INSERT语句这只适用。

解析列表时,我不需要事先调用多少[]个table[key1][key2][...]


紧密

Answers:


181

您甚至不需要定义类就可以做到:

from collections import defaultdict

nested_dict = lambda: defaultdict(nested_dict)
nest = nested_dict()

nest[0][1][2][3][4][5] = 6

9
真甜!但是,如果我要离开通过标准(int,列表等)工厂进行初始化怎么样?例如,我想说: table[0][1][2][3][4][5] += 1
rikb

1
有没有办法对内置的dict和.get()做同样的事情?
Aleksandr Levchuk 2014年

1
l(dict)类:__ missing __ = lambda a,b:a.setdefault(b,l()),然后从table = l()继续执行
Hugo Walter

1
PyCharm说它违反了PEP 8:“请勿使用def分配lambda表达式”。有什么方法可以消除警告吗?
NaturalBornCamper

4
def nested_dict():返回defaultdict(nested_dict),但我更喜欢lambda版本。它看起来有点神秘;-)
雨果·沃尔特

18

您的示例表明,在任何级别上都可以有一个值,也可以有一个子元素字典。这称为一棵树,并且有许多可用的实现。这是一:

from collections import defaultdict
class Tree(defaultdict):
    def __init__(self, value=None):
        super(Tree, self).__init__(Tree)
        self.value = value

root = Tree()
root.value = 1
root['a']['b'].value = 3
print root.value
print root['a']['b'].value
print root['c']['d']['f'].value

输出:

1
3
None

您可以通过使用JSON编写输入并将json.load其用作嵌套字典的结构来读取内容,从而执行类似的操作。


我认为该value构造是不必要的,至少就所提出的问题而言。只需删除value对字典键的引用并将其直接分配给字典键即可。
詹森·库姆斯

+1:尽管valuearg / attribute并不是必需的。
martineau 2011年

4
@Martineau @杰森。该value实例变量是必要的,因为否则,当你直接分配到节点(见我对杰森的优雅的解决方案评论)你会失去孩子。干预__setitem__将提供一个更加健壮的解决方案,但是对于简单的需求而言,这将是一个过于复杂的解决方案。
阿帕拉拉2011年

我不清楚如何修改collection属性是alist而不是a的其他答案int/float。这个答案很清楚, self.value = []我到底在找什么!
benjaminmgross

10

我会用一个dict定义的子类来做到这一点__missing__

>>> class NestedDict(dict):
...     def __missing__(self, key):
...             self[key] = NestedDict()
...             return self[key]
...
>>> table = NestedDict()
>>> table['A']['B1']['C1'] = 1
>>> table
{'A': {'B1': {'C1': 1}}}

您无法直接使用defaultdict来执行此操作,因为defaultdict在初始化时需要工厂函数,但是在初始化时,无法描述相同的defaultdict。上面的构造与默认dict的作用相同,但是由于它是一个命名类(NestedDict),当遇到缺少的键时,它可以引用自身。也可以继承defaultdict和override __init__


这还不够。如果尝试将出现错误table['A']['B1']['C1']['D2'] = 2。节点必须能够保存值子节点。
阿帕拉拉2011年

3
@Apalala:实际上,从OP的示例输入来看,似乎节点仅需要能够容纳一个值或一个子节点,而不必同时包含两个值-这就是为什么@Jason和我声称您的答案的value属性是不必要的。
martineau 2011年

@martinau MHO除非将其解析为树,否则一切都会变得不稳定(容易出错)。语法和实现无关。是不是需要树形结构的问题?我的观点是,除非有令人信服的理由,否则不应强迫设计采用漂亮的语法。吻。
2011年

@Apalala我知道这很旧。但是我们如何实现defaultdict既包含价值观又包含孩子的a?
Halcyon亚伯拉罕·拉米雷斯

@HalcyonAbrahamRamirez看看这个问题中阿帕拉拉的答案。
詹森·库姆斯

9

我认为递归字典的最简单实现就是这样。只有叶节点可以包含值。

# Define recursive dictionary
from collections import defaultdict
tree = lambda: defaultdict(tree)

用法:

# Create instance
mydict = tree()

mydict['a'] = 1
mydict['b']['a'] = 2
mydict['c']
mydict['d']['a']['b'] = 0

# Print
import prettyprint
prettyprint.pp(mydict)

输出:

{
  "a": 1, 
  "b": {
    "a": 1
  }, 
  "c": {},
  "d": {
    "a": {
      "b": 0
    }
  }
}

刚刚注意到我的帖子是#2的重复。抱歉
Bouke Versteegh 2012年

这可能是愚蠢的,但是我认为该示例非常说明性和有用,因此我想肯定您添加了一些有用的内容。
疯狂物理学家,2018年

5

这与上述等效,但避免使用lambda表示法。也许更容易阅读?

def dict_factory():
   return defaultdict(dict_factory)

your_dict = dict_factory()

另外-从评论中-如果您想从现有字典更新,则只需调用

your_dict[0][1][2].update({"some_key":"some_value"})

为了给dict添加值。


此解决方案不提供传递初始值的功能。出于这个原因,我认为Dan O'Huiginn的解决方案(通过DVD Avins发布)略胜一筹。
Scott P.

4

Dan O'Huiginn在2010年的日记中发布了一个非常不错的解决方案:

http://ohuiginn.net/mt/2010/07/nested_dictionaries_in_python.html

>>> class NestedDict(dict):
...     def __getitem__(self, key):
...         if key in self: return self.get(key)
...         return self.setdefault(key, NestedDict())


>>> eggs = NestedDict()
>>> eggs[1][2][3][4][5]
{}
>>> eggs
{1: {2: {3: {4: {5: {}}}}}}

1
当我想快速创建嵌套字典时,我发现这种方法很好。如果我想“重新启用” KeyError,可以使用轻松转换回标准词典dict()
JS。

return self.setdefault(key, NestedDict())足够了。无需if。
Scott P.

2

允许常规字典初始化的可能性稍有不同:

from collections import defaultdict

def superdict(arg=()):
    update = lambda obj, arg: obj.update(arg) or obj
    return update(defaultdict(superdict), arg)

例:

>>> d = {"a":1}
>>> sd = superdict(d)
>>> sd["b"]["c"] = 2

1

添加到@Hugo
的最大深度:

l=lambda x:defaultdict(lambda:l(x-1)) if x>0 else defaultdict(dict)
arr = l(2)

1

您可以通过递归实现defaultdict

from collections import defaultdict

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

重要的是,the_tree在此处保护闭包(“私有”本地功能范围)中的默认工厂名称。避免使用单行lambda版本,该版本由于Python的后期绑定关闭而存在bug而,请使用def改用来实现。

使用lambda接受的答案存在一个缺陷,即实例必须依赖nested_dict外部作用域中存在的名称。如果由于某种原因无法解析工厂名称(例如,工厂名称被反弹或删除),则先前存在的实例也将被巧妙地破坏:

>>> nested_dict = lambda: defaultdict(nested_dict)
>>> nest = nested_dict()
>>> nest[0][1][2][3][4][6] = 7
>>> del nested_dict
>>> nest[8][9] = 10
# NameError: name 'nested_dict' is not defined
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.