设置嵌套字典值并创建中间键

10

最近我好像看到有一种方法可以实现这个。假设我有一个空字典,我想在其中的一个嵌套字典中设置值,但很显然,那个嵌套字典尚未创建。是否有一种一行的方式来创建中间键?这就是我想要做的:

mydict = {}
mydict['foo']['bar']['foobar'] = 25
如果你执行这段代码,会出现针对 'foo' 的 KeyError 异常。是否有一种方法可以创建中间键?
谢谢。

3个回答

19
from collections import defaultdict
recursivedict = lambda: defaultdict(recursivedict)
mydict = recursivedict()
当你访问mydict['foo']时,它会将mydict['foo']设置为另一个recursivedict。它实际上会构造一个recursivedict用于mydict['foo']['bar']['foobar'],但是在将其赋值为25时,它将被丢弃。

这正是我记得读到的内容 - 谢谢。 - skandocious
这可能会导致错误:mydict['foo'] = 15⏎ mydict['foo']['bar']['foobar'] = 25。在大型代码库中,原始问题的提出者可能无法记住先前分配的值。 - nehem
@itsneo 说得对。我认为没有好的解决方法;你必须使 __getitem__ 返回包装器,覆盖存储元素的 __getitem__,这将使将列表或其他内容放入字典变得棘手。 - Danica
太棒了!谢谢,这就是我在寻找的机器人! - darkless

5

另一种选择(取决于您的用途)是使用元组作为键,而不是嵌套的字典:

mydict = {}
mydict['foo', 'bar', 'foobar'] = 25

这将完美地运作,除非您想在任何时候获取树的一个分支(在这种情况下,您无法获取mydict['foo'])。
如果您知道要嵌套多少层,则还可以使用functools.partial而不是lambda。
from functools import partial
from collections import defaultdict

tripledict = partial(defaultdict, partial(defaultdict, dict))
mydict = tripledict()
mydict['foo']['bar']['foobar'] = 25

有些人发现这种方式更易读,并且比基于lambda的解决方案创建实例更快:
python -m timeit -s "from functools import partial" -s "from collections import defaultdict" -s "tripledefaultdict = partial(defaultdict, partial(defaultdict, dict))" "tripledefaultdict()"
1000000 loops, best of 3: 0.281 usec per loop

python -m timeit -s "from collections import defaultdict" -s "recursivedict = lambda: defaultdict(recursivedict)" "recursivedict()"
1000000 loops, best of 3: 0.446 usec per loop

然而,一如既往,没有必要优化,直到你知道瓶颈在哪里,因此在优先考虑最有用和易读的内容之前,请选择最快的内容。


有趣的东西。我从未想过将元组用作字典键。虽然这对我的情况没有帮助,但肯定很有用 - 谢谢! - skandocious
当然,通过基于元组的解决方案,可以获取树分支,例如 prefix = ('foo', 'bar'); l = len(prefix); branch = { k[l:]: v for k, v in mydict.items() if k[:l] == prefix} -- 尽管需要遍历所有键。 - Danica
@Dougal 是的,在那种情况下,最好只使用嵌套的“dict”。 - Gareth Latty
@Lattyware 哇!这真是令人惊讶。你关于字符串字典的快速路径的评论可能是主要原因,但我想元组情况下是否还有更多的哈希冲突。 - Danica
@Dougal 是的,我也不太确定为什么会这样。显然那只是一个小测试,所以要持保留态度,但仍值得思考。 - Gareth Latty
显示剩余2条评论

0

不确定你为什么想要这样做,但:

>>> from collections import defaultdict as dd
>>> mydict = dd(lambda: dd(lambda: {}))
>>> mydict['foo']['bar']['foobar'] = 25
>>> mydict
defaultdict(<function <lambda> at 0x021B8978>, {'foo': defaultdict(<function <lambda> at 0x021B8618>, {'bar': {'foobar': 25}})})

这只允许您嵌套到三层; 您想要函数是递归的,就像[我的回答](https://dev59.com/YGkv5IYBdhLWcg3w_Vmn#10218517)一样,才能继续执行。 - Danica
@Dougal:当我写这篇文章时,你的回答没有提到这一点。 - MattH
这个解决方案并没有错,但是我可能需要比3层更深入的层次,所以Dougal的回答更好。谢谢! - skandocious
@skandocious:我建议在这种方法上谨慎行事。当我第一次停止使用Perl编码(11年前),我真的很想念动态定义任意深度数据结构,但仅持续了几周,之后我从未需要或使用它们。 - MattH
@MattH 在这种情况下,这只是方便的问题。当我在几个循环中跳来跳去并填充我的嵌套结构时,不必在每个循环迭代之前不断检查是否存在嵌套字典,然后再将值放入其中,这真的很好。 - skandocious
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接