我正在构建一些Python代码来读取和操作深层嵌套的dicts(最终用于与JSON服务进行交互,但是出于其他目的它会很棒)我正在寻找一种方便的读取/设置/更新dict中的值很深,不需要很多代码。
@see Python: Recursively access dict via attributes as well as index access? - Curt Hagenlocher的“DotDictify”解决方案非常有说服力。我也喜欢Ben Alman在http://benalman.com/projects/jquery-getobject-plugin/中为JavaScript提供的内容。以某种方式将两者结合起来会很棒。
在Curt Hagenlocher和Ben Alman的例子的基础上,拥有像以下这样的能力在Python中会很棒:
>>> my_obj = DotDictify()
>>> my_obj.a.b.c = {'d':1, 'e':2}
>>> print my_obj
{'a': {'b': {'c': {'d': 1, 'e': 2}}}}
>>> print my_obj.a.b.c.d
1
>>> print my_obj.a.b.c.x
None
>>> print my_obj.a.b.c.d.x
None
>>> print my_obj.a.b.c.d.x.y.z
None
有任何想法是否可行,如果可行,如何修改DotDictify解决方案?
或者,可以使get方法接受点符号(并添加补充集方法)但是对象符号确定更清晰。
>>> my_obj = DotDictify()
>>> my_obj.set('a.b.c', {'d':1, 'e':2})
>>> print my_obj
{'a': {'b': {'c': {'d': 1, 'e': 2}}}}
>>> print my_obj.get('a.b.c.d')
1
>>> print my_obj.get('a.b.c.x')
None
>>> print my_obj.get('a.b.c.d.x')
None
>>> print my_obj.get('a.b.c.d.x.y.z')
None
这种类型的交互对于处理深层嵌套的dicts非常有用。有人知道另一种策略(或示例代码片段/库)吗?
答案 0 :(得分:33)
你的第一个规范的问题是Python无法在__getitem__
中告诉你,在my_obj.a.b.c.d
,你是否会在下一个不存在的树上继续前进,在这种情况下它需要返回一个对象使用__getitem__
方法,这样您就不会被AttributeError
抛出,或者如果您想要一个值,在这种情况下它需要返回None
。
我认为,在上面的每一个案例中,你应该期望它抛出KeyError
而不是返回None
。原因是您无法判断None
是否表示“无密钥”或“某人实际存储None
在该位置”。对于此行为,您只需执行dotdictify
,删除marker
,然后将__getitem__
替换为:
def __getitem__(self, key):
return self[key]
因为您真正想要的是dict
__getattr__
和__setattr__
。
可能有一种方法可以完全删除__getitem__
并说__getattr__ = dict.__getitem__
之类的内容,但我认为这可能会过度优化,如果您以后决定要{{1像__getitem__
原来那样创建树,在这种情况下你可以将它改为:
dotdictify
我不喜欢原始def __getitem__(self, key):
if key not in self:
dict.__setitem__(self, key, dotdictify())
return dict.__getitem__(self, key)
中的marker
商家。
第二个规范(覆盖dotdictify
和get()
)是正常的set()
有一个dict
,其运作方式与您描述的不同,甚至没有get()
(虽然它有一个set
,它是setdefault()
的逆操作。人们期望get()
采用两个参数,如果找不到密钥则第二个是默认参数。
如果您想扩展get
和__getitem__
以处理虚线键表示法,则需要将__setitem__
修改为:
doctictify
测试代码:
class dotdictify(dict):
def __init__(self, value=None):
if value is None:
pass
elif isinstance(value, dict):
for key in value:
self.__setitem__(key, value[key])
else:
raise TypeError, 'expected dict'
def __setitem__(self, key, value):
if '.' in key:
myKey, restOfKey = key.split('.', 1)
target = self.setdefault(myKey, dotdictify())
if not isinstance(target, dotdictify):
raise KeyError, 'cannot set "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target))
target[restOfKey] = value
else:
if isinstance(value, dict) and not isinstance(value, dotdictify):
value = dotdictify(value)
dict.__setitem__(self, key, value)
def __getitem__(self, key):
if '.' not in key:
return dict.__getitem__(self, key)
myKey, restOfKey = key.split('.', 1)
target = dict.__getitem__(self, myKey)
if not isinstance(target, dotdictify):
raise KeyError, 'cannot get "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target))
return target[restOfKey]
def __contains__(self, key):
if '.' not in key:
return dict.__contains__(self, key)
myKey, restOfKey = key.split('.', 1)
target = dict.__getitem__(self, myKey)
if not isinstance(target, dotdictify):
return False
return restOfKey in target
def setdefault(self, key, default):
if key not in self:
self[key] = default
return self[key]
__setattr__ = __setitem__
__getattr__ = __getitem__
答案 1 :(得分:2)
我曾经使用类似的东西为应用程序构建类似Trie的东西。我希望它有所帮助。
class Trie:
"""
A Trie is like a dictionary in that it maps keys to values.
However, because of the way keys are stored, it allows
look up based on the longest prefix that matches.
"""
def __init__(self):
# Every node consists of a list with two position. In
# the first one,there is the value while on the second
# one a dictionary which leads to the rest of the nodes.
self.root = [0, {}]
def insert(self, key):
"""
Add the given value for the given key.
>>> a = Trie()
>>> a.insert('kalo')
>>> print(a)
[0, {'k': [1, {'a': [1, {'l': [1, {'o': [1, {}]}]}]}]}]
>>> a.insert('kalo')
>>> print(a)
[0, {'k': [2, {'a': [2, {'l': [2, {'o': [2, {}]}]}]}]}]
>>> b = Trie()
>>> b.insert('heh')
>>> b.insert('ha')
>>> print(b)
[0, {'h': [2, {'a': [1, {}], 'e': [1, {'h': [1, {}]}]}]}]
"""
# find the node to append the new value.
curr_node = self.root
for k in key:
curr_node = curr_node[1].setdefault(k, [0, {}])
curr_node[0] += 1
def find(self, key):
"""
Return the value for the given key or None if key not
found.
>>> a = Trie()
>>> a.insert('ha')
>>> a.insert('ha')
>>> a.insert('he')
>>> a.insert('ho')
>>> print(a.find('h'))
4
>>> print(a.find('ha'))
2
>>> print(a.find('he'))
1
"""
curr_node = self.root
for k in key:
try:
curr_node = curr_node[1][k]
except KeyError:
return 0
return curr_node[0]
def __str__(self):
return str(self.root)
def __getitem__(self, key):
curr_node = self.root
for k in key:
try:
curr_node = curr_node[1][k]
except KeyError:
yield None
for k in curr_node[1]:
yield k, curr_node[1][k][0]
if __name__ == '__main__':
a = Trie()
a.insert('kalo')
a.insert('kala')
a.insert('kal')
a.insert('kata')
print(a.find('kala'))
for b in a['ka']:
print(b)
print(a)
答案 2 :(得分:2)
对于其他googlers:我们现在有addict:
pip install addict
和
mapping.a.b.c.d.e = 2
mapping
{'a': {'b': {'c': {'d': {'e': 2}}}}}
我广泛使用它。
为了使用虚线路径,我找到了dotted:
obj = DottedDict({'hello': {'world': {'wide': 'web'}}})
obj['hello.world.wide'] == 'web' # true
答案 3 :(得分:1)
较早的答案中有一些不错的技巧,但是它们都需要用自定义的替换标准的Python数据结构(字典等),并且不适用于不是有效属性名称的键。
这几天,我们可以使用为此目的而构建的,纯Python,Python 2/3兼容的库glom来做得更好。以您的示例为例:
import glom
target = {} # a plain dictionary we will deeply set on
glom.assign(target, 'a.b.c', {'d': 1, 'e': 2}, missing=dict)
# {'a': {'b': {'c': {'e': 2, 'd': 1}}}}
注意missing=dict
,用于自动创建字典。我们可以使用glom的deep-get轻松获得价值:
glom.glom(target, 'a.b.c.d')
# 1
glom可以做很多事情,尤其是在深入获取和设置方面。我应该知道,因为(完全披露)我创建了它。这意味着如果您发现差距,则应该let me know!