我正在使用What is the best way to implement nested dictionaries?上的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
{}
如果第二个给出错误,那将是很好的,因为['foo'] ['eggs']尚未被定义...
答案 0 :(得分: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'
答案 1 :(得分:0)
我认为没有任何方法可以完全按照您的要求进行操作,但如果您对设置键的方式略有修改可以通过使用常规词典获得非常相似的东西。< / p>
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