多层次的';collection.defaultdict';用Python

多层次的';collection.defaultdict';用Python,python,dictionary,nested,Python,Dictionary,Nested,多亏了一些优秀的人,我发现了collections.defaultdict提供的可能性,特别是在可读性和速度方面。我成功地使用了它们 现在我想实现三个级别的字典,最上面的两个是defaultdict,最下面的一个是int。我找不到合适的方法来做这件事。以下是我的尝试: from collections import defaultdict d = defaultdict(defaultdict) a = [("key1", {"a1":22, "a2":33}), ("key2", {

多亏了一些优秀的人,我发现了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
,首先是为了避免为每个新键初始化字典

还有更优雅的建议吗

谢谢蟒蛇

使用:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(int))
这将创建一个新的
defaultdict(int)
,每当在
d
中访问一个新的键时,查看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
的请求,我们可以通过定义
\uuuuuu add\uuuuu
方法覆盖加法来进行扩展,使其行为更像一个
collections.Counter()

首先将调用
\uuuuuu missing\uuuuu
来创建一个新的空值,该值将被传递到
\uuuuuuu add\uuuuu
中。我们测试该值,将空值计数为
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}}}
我们可以只提供一个默认的0值,然后尝试以下操作,而不是检查参数是否是一个数字(非常非python,amirite!):

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的另一种方法是使用分部对象而不是lambda:

from functools import partial
...
d = defaultdict(partial(defaultdict, int))
这将起作用,因为defaultdict类在模块级别是全局可访问的:

“除非函数[或]在此 它包装的是全局可访问的…在它的__名称下__ (在其_模块内) --


晚会迟到了,但出于随意的深度,我发现自己在做这样的事情:

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
没有错误了。 不管嵌套了多少层。 弹出也没有错误


dd=DefaultDict({“1”:333333})

感谢@miles82的链接(以及编辑@voyager)。这种方法有多安全?不幸的是,这种解决方案没有保留defaultdict最简单的部分,即编写类似于D['key']+=1的内容而不必担心密钥的存在。这是我使用defaultdict的主要功能。。。但我可以想象动态深化字典也很方便。@rschwieb您可以通过添加add方法来增加写入+=1的能力。唯一的问题是它不会被pickle,意思是说,
multiprocessing
对来回发送这些数据感到不高兴。@Noah:如果您使用命名模块级函数而不是lambda,它将被pickle。@ScienceFriction有什么具体的需要帮助的吗?当访问
d[new_key]
时,它将调用lambda,该lambda将创建一个新的
defaultdict(int)
。当访问
d[existing_key][new_key 2]
时,将创建一个新的
int
。这太棒了。我似乎每天都在向Python重申我的婚姻誓言。想了解更多关于在
多处理中使用此方法的详细信息,以及命名模块级函数是什么?这就跟进了。这些是否应该引发NotImplemented而不是ValueError?
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]: ''