1

我正在使用使用 AutoVivification 类答案实现的嵌套字典,实现嵌套字典的最佳方法是什么?; 即

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['foo']['bar'] = 'spam'

因此允许在字典中任意嵌套。有没有办法修改类,以便可以使用任意一组键为成员分配值,但在尝试访问/读取成员时只允许以前定义的键集?例如,

print a['foo']['bar']
print a['foo']['eggs']

目前输出

spam
{}

如果第二个给出错误,那就太好了,因为 a['foo']['eggs'] 尚未定义......

4

2 回答 2

2

您将遇到的问题是,为了在嵌套字典上设置项目,您必须首先能够获取所有父项目。例如:

d[1][2][3] = 42

需要得到 d[1][2]才能设置 d[1][2][3]。当您访问中间字典时,无法知道分配是否正在进行中,因此使分配工作的唯一方法是始终在访问时创建子字典。(您可以返回某种代理对象而不是创建子字典,并将中间字典的创建推迟到分配,但是当您访问不存在的路径时仍然不会出错。)

解决此问题的最简单方法是使用单个元组键而不是重复的子键。换句话说,d[1][2][3]您将设置而不是设置d[1, 2, 3]。赋值是独立的操作:它们不需要获取任何中间嵌套级别,因此您可以在赋值时创建中间级别。

作为奖励,您可能会发现在传递多个键时使用元组更加简单,因为您可以将它们插入[]并获得您想要的项目。

可以使用单个字典来执行此操作,使用元组作为键。但是,这会丢失数据的层次结构。下面的实现使用子字典。使用了一个字典子类node,以便我们可以在字典上分配一个属性来表示该位置节点的值;这样,我们可以在中间节点和叶子上存储值。(它有一个__repr__显示节点值及其子节点的方法,如果有的话。)类的__setitem__方法tupledict在分配元素时处理创建中间节点。__getitem__遍历节点以找到您想要的值。(如果要将单个节点作为节点访问,可以使用get()一次接一个。)

class tupledict(dict):

    class node(dict):
        def __repr__(self):
            if self:
                if hasattr(self, "value"):
                    return repr(self.value) + ", " + dict.__repr__(self)
                return dict.__repr__(self)
            else:
                return repr(self.value)

    def __init__(self):
        pass

    def __setitem__(self, key, value):
        if not isinstance(key, tuple):   # handle single value
            key = [key]
        d = self
        for k in key:
            if k not in d:
                dict.__setitem__(d, k, self.node())
            d = dict.__getitem__(d, k)
        d.value = value

    def __getitem__(self, key):
        if not isinstance(key, tuple):
            key = [key]
        d = self
        for k in key:
            try:
                d = dict.__getitem__(d, k)
            except KeyError:
                raise KeyError(key[0] if len(key) == 1 else key)
        try:
            return d.value
        except AttributeError:
            raise KeyError(key[0] if len(key) == 1 else key)

用法:

td = tupledict()
td['foo', 'bar'] = 'spam'
td['foo', 'eggs']   # KeyError

key = 'foo', 'bar'
td[key]    # 'spam'
于 2013-08-14T18:09:28.093 回答
0

我认为没有任何方法可以完全按照您的要求进行操作,但是如果您可以对设置键的方式稍作修改,则只需使用常规字典即可获得非常相似的结果。

def nested_dict_set(d, keys, value):
    for k in keys[:-1]:
        d = d.setdefault(k, {})
    d[keys[-1]] = value

a = {}
nested_dict_set(a, ['foo', 'bar'], 'spam')
print a['foo']['bar']
print a['foo']['eggs']  # raises a KeyError
于 2013-08-14T16:47:04.043 回答