Python:defaultdict的defaultdict?


322

有没有一种方法可以defaultdict(defaultdict(int))使以下代码正常工作?

for x in stuff:
    d[x.a][x.b] += x.c_int

d需要临时构建,具体取决于x.ax.b元素。

我可以使用:

for x in stuff:
    d[x.a,x.b] += x.c_int

但后来我将无法使用:

d.keys()
d[x.a].keys()

6
看到类似的问题在Python中实现嵌套字典的最佳方法是什么?。Wikipedia关于Autovivification的文章中还有一些可能有用的信息。
martineau 2014年

Answers:


570

是这样的:

defaultdict(lambda: defaultdict(int))

当您尝试访问不存在的键时,将调用的参数defaultdict(在这种情况下为lambda: defaultdict(int))。它的返回值将设置为该密钥的新值,这意味着在我们的情况下,d[Key_doesnt_exist]将为defaultdict(int)

如果尝试从最后一个defaultdict访问密钥,即d[Key_doesnt_exist][Key_doesnt_exist]它将返回0,这是最后一个defaultdict的参数的返回值int()


7
它很棒!您能解释一下这种语法背后的合理性吗?
乔纳森

37
@Jonathan:是的,当您尝试访问不存在的键时,将调用a defaultdict(在这种情况下为lambda : defaultdict(int))的参数,并且其返回值将设置为该键的新值,这意味着在我们的例子中,值d[Key_dont_exist]将为defaultdict(int),如果您尝试从最后一个defaultdict访问键,即d[Key_dont_exist][Key_dont_exist]它将返回0,这是最后一个defaultdictie 的参数的返回值int(),希望对您有所帮助。
mouad 2011年

25
参数to defaultdict应该是一个函数。defaultdict(int)是字典,lambda: defaultdict(int)而是返回字典的函数。
has2k1 2012年

27
@ has2k1这是不正确的。defaultdict的参数必须是可调用的。Lambda是可调用的。
Niels Bom 2013年

2
@RickyLevi,如果您想工作,可以说:defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
darophi

51

defaultdict构造函数的参数是用于构建新元素的函数。因此,让我们使用lambda!

>>> from collections import defaultdict
>>> d = defaultdict(lambda : defaultdict(int))
>>> print d[0]
defaultdict(<type 'int'>, {})
>>> print d[0]["x"]
0

从Python 2.7开始,使用Counter有了一个更好的解决方案

>>> from collections import Counter
>>> c = Counter()
>>> c["goodbye"]+=1
>>> c["and thank you"]=42
>>> c["for the fish"]-=5
>>> c
Counter({'and thank you': 42, 'goodbye': 1, 'for the fish': -5})

一些额外功能

>>> c.most_common()[:2]
[('and thank you', 42), ('goodbye', 1)]

有关更多信息,请参见PyMOTW-集合-容器数据类型Python文档-集合


5
只是为了完成此处的圈,您将要使用d = defaultdict(lambda : Counter())而不是d = defaultdict(lambda : defaultdict(int))专门解决最初提出的问题。
gumption

3
@gumption d = defaultdict(Counter())在这种情况下,您无需使用lambda即可
Deb

3
@Deb有一个小错误-删除内部括号,以便传递可调用Counter对象而不是对象。那是:d = defaultdict(Counter)
Dillon Davis

29

我发现使用起来稍微更优雅partial

import functools
dd_int = functools.partial(defaultdict, int)
defaultdict(dd_int)

当然,这与lambda相同。


1
Partial在这里也比lambda好,因为它可以递归地应用:)参见下面关于通用嵌套defaultdict工厂方法的回答。
Campi

@Campi你不需要局部递归应用,AFAICT
克莱门特

10

作为参考,可以通过以下方式实现通用的嵌套defaultdict工厂方法:

from collections import defaultdict
from functools import partial
from itertools import repeat


def nested_defaultdict(default_factory, depth=1):
    result = partial(defaultdict, default_factory)
    for _ in repeat(None, depth - 1):
        result = partial(defaultdict, result)
    return result()

深度定义了default_factory使用中定义的类型之前嵌套字典的数量。例如:

my_dict = nested_defaultdict(list, 3)
my_dict['a']['b']['c'].append('e')

你能举个例子吗?无法按照我期望的方式工作。ndd = nested_defaultdict(dict) .... ndd['a']['b']['c']['d'] = 'e'KeyError: 'b'
David Marx

嗨,大卫,您需要定义字典的深度,在示例3中(因为您将default_factory也定义为字典。nested_defaultdict(dict,3)也将为您工作。–
Campi

这超级有帮助,谢谢!我注意到的一件事是,这会在处创建default_dict depth=0,如果调用时深度未知,则可能并不总是需要这样。通过if not depth: return default_factory()在函数顶部添加line可以轻松修复,尽管可能存在更优雅的解决方案。
布伦丹

9

先前的答案已经解决了如何制作两级或n级defaultdict。在某些情况下,您需要无限个:

def ddict():
    return defaultdict(ddict)

用法:

>>> d = ddict()
>>> d[1]['a'][True] = 0.5
>>> d[1]['b'] = 3
>>> import pprint; pprint.pprint(d)
defaultdict(<function ddict at 0x7fcac68bf048>,
            {1: defaultdict(<function ddict at 0x7fcac68bf048>,
                            {'a': defaultdict(<function ddict at 0x7fcac68bf048>,
                                              {True: 0.5}),
                             'b': 3})})

1
我喜欢这个。它非常简单,但是非常有用。谢谢!
rosstex

6

其他人已经正确回答了您如何使以下各项正常工作的问题:

for x in stuff:
    d[x.a][x.b] += x.c_int

一种替代方法是使用元组作为键:

d = defaultdict(int)
for x in stuff:
    d[x.a,x.b] += x.c_int
    # ^^^^^^^ tuple key

这种方法的好处是它很简单并且可以轻松扩展。如果您需要三个层次的映射,只需使用一个三项元组作为键。


4
此解决方案意味着要获取所有d [xa]并非易事,因为您需要对每个键进行内省,以查看其是否具有xa作为元组的第一个元素。
马修·辛克尔

5
如果您想嵌套3个级别,则只需将其定义为3个级别:d = defaultdict(lambda:defaultdict(lambda:defaultdict(int)))
Matthew Schinckel
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.