Python中的“ collection.defaultdict ”的多个级别

感谢SO上的一些伟大的人,我发现了collections.defaultdict所提供的可能性,特别是在可读性和速度方面。我已经成功地使用了它们。

现在我想实现三个级别的字典,最上面的两个是defaultdict,最下面的一个是int。我没有找到合适的方法来做这件事。以下是我的尝试:

from collections import defaultdict
d = defaultdict(defaultdict)
a = [("key1", {"a1":22, "a2":33}),
("key2", {"a1":32, "a2":55}),
("key3", {"a1":43, "a2":44})]
for i in a:
d[i[0]] = i[1]

现在,这是可行的,但以下所需的行为却不可行:

d["key4"]["a1"] + 1

我怀疑我应该在某处声明第二级defaultdict的类型int,但我没有找到在何处或如何这样做。

首先,我使用defaultdict的原因是为了避免必须为每个新键初始化字典。

还有更优雅的建议吗?

感谢Pythoneers!

88861 次浏览

使用:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(int))

这将在访问d中的新密钥时创建新的defaultdict(int)

查看Nosklo的答案在这里,以获得更一般的解决方案。

class AutoVivification(dict):
"""Implementation of perl's autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value

测试:

a = AutoVivification()


a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6


print a

产量:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

根据@rschwieb对D['key'] += 1的请求,我们可以通过定义__add__方法覆盖加法来扩展前面的,以使其行为更像collections.Counter()

将调用第一个__missing__来创建一个新的空值,该值将被传递到__add__中。我们测试该值,将空值计数为False

有关覆盖的详细信息,请参阅模拟数字类型

from numbers import Number




class autovivify(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value


def __add__(self, x):
""" override addition for numeric types when self is empty """
if not self and isinstance(x, Number):
return x
raise ValueError


def __sub__(self, x):
if not self and isinstance(x, Number):
return -1 * x
raise ValueError

示例:

>>> import autovivify
>>> a = autovivify.autovivify()
>>> a
{}
>>> a[2]
{}
>>> a
{2: {}}
>>> a[4] += 1
>>> a[5][3][2] -= 1
>>> a
{2: {}, 4: 1, 5: {3: {2: -1}}}

而不是检查参数是一个数字(非常非Python,amirite!)我们可以只提供一个默认值0,然后尝试操作:

class av2(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value


def __add__(self, x):
""" override addition when self is empty """
if not self:
return 0 + x
raise ValueError


def __sub__(self, x):
""" override subtraction when self is empty """
if not self:
return 0 - x
raise ValueError

另一种创建可pickle化的、嵌套的defaultdict的方法是使用partial对象而不是lambda:

from functools import partial
...
d = defaultdict(partial(defaultdict, int))

这将起作用,因为DefaultDict类在模块级别是全局可访问的:

您不能pickle分部对象,除非函数[or in this Case,Class]它包装的是全局可访问..其__名称为__。 (在其__模块__内) --酸洗包裹部分功能

聚会迟到了,但对于任意的深度,我发现自己在做这样的事情:

from collections import defaultdict


class DeepDict(defaultdict):
def __call__(self):
return DeepDict(self.default_factory)

这里的技巧基本上是使DeepDict实例本身成为构造缺失值的有效工厂。现在我们可以这样做

dd = DeepDict(DeepDict(list))
dd[1][2].extend([3,4])
sum(dd[1][2])  # 7


ddd = DeepDict(DeepDict(DeepDict(list)))
ddd[1][2][3].extend([4,5])
sum(ddd[1][2][3])  # 9
def _sub_getitem(self, k):
try:
# sub.__class__.__bases__[0]
real_val = self.__class__.mro()[-2].__getitem__(self, k)
val = '' if real_val is None else real_val
except Exception:
val = ''
real_val = None
# isinstance(Avoid,dict)也是true,会一直递归死
if type(val) in (dict, list, str, tuple):
val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
# 重新赋值当前字典键为返回值,当对其赋值时可回溯
if all([real_val is not None, isinstance(self, (dict, list)), type(k) is not slice]):
self[k] = val
return val




def _sub_pop(self, k=-1):
try:
val = self.__class__.mro()[-2].pop(self, k)
val = '' if val is None else val
except Exception:
val = ''
if type(val) in (dict, list, str, tuple):
val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
return val




class DefaultDict(dict):
def __getitem__(self, k):
return _sub_getitem(self, k)


def pop(self, k):
return _sub_pop(self, k)


In[8]: d=DefaultDict()
In[9]: d['a']['b']['c']['d']
Out[9]: ''
In[10]: d['a']="ggggggg"
In[11]: d['a']
Out[11]: 'ggggggg'
In[12]: d['a']['pp']
Out[12]: ''
再次

没有错误。 无论嵌套多少层。 弹出无错误也

DD=默认字典({“ 1 ”:333333})