假设我有一个像这样的多级字典
mydict = {
'first': {
'second': {
'third': {
'fourth': 'the end'
}
}
}
}
我想像这样访问它
test = get_entry(mydict, 'first.second.third.fourth')
到目前为止我所拥有的是
def get_entry(dict, keyspec):
keys = keyspec.split('.')
result = dict[keys[0]]
for key in keys[1:]:
result = dict[key]
return result
有更有效的方法吗?根据%timeit,函数的运行时间是1.26us,而像这样的标准方式访问字典
foo = mydict['first']['second']['third']['fourth']
需要541ns。如果可能的话,我正在寻找将其修剪到800ns的方法。
由于
答案 0 :(得分:12)
真的只有一个解决方案。重建你的字典。但只做一次。
def recursive_flatten(mydict):
d = {}
for k, v in mydict.items():
if isinstance(v, dict):
for k2, v2 in recursive_flatten(v).items():
d[k + '.' + k2] = v2
else:
d[k] = v
return d
In [786]: new_dict = recursive_flatten(mydict); new_dict
Out[786]: {'first.second.third.fourth': 'the end'}
(还有一些测试)
In [788]: recursive_flatten({'x' : {'y' : 1, 'z' : 2}, 'y' : {'a' : 5}, 'z' : 2})
Out[788]: {'x.y': 1, 'x.z': 2, 'y.a': 5, 'z': 2}
In [789]: recursive_flatten({'x' : 1, 'y' : {'x' : 234}})
Out[789]: {'x': 1, 'y.x': 234}
从此处开始,每次访问都会成为固定时间。
现在,只需使用new_dict['first.second.third.fourth']
访问您的值即可。应该适用于任何不包含自引用的任意嵌套字典。
请注意,每个解决方案都有其公平的权衡,这也不例外。除非您在数据上发出数百万条查询,以便预处理是可接受的开销,否则就是这样。使用其他解决方案,您只是回避问题而不是解决问题 - 这是处理字典的结构。 OTOH,如果您要在许多这样的类似数据结构上一次 ,那么仅针对单个查询进行预处理是没有意义的,在这种情况下,可能更喜欢其他解决方案之一。
答案 1 :(得分:11)
通过将代码收紧一点,但通过使用缓存来分割字符串,我的性能提升了20%,性能提升了20%。如果您多次使用相同的规格,那只会产生影响。以下是示例实现和要测试的配置文件脚本。
test.py
mydict = {
'first': {
'second': {
'third': {
'fourth': 'the end'
}
}
}
}
# original
def get_entry(dict, keyspec):
keys = keyspec.split('.')
result = dict[keys[0]]
for key in keys[1:]:
result = result[key]
return result
# tighten up code
def get_entry_2(mydict, keyspec):
for key in keyspec.split('.'):
mydict = mydict[key]
return mydict
# use a cache
cache = {}
def get_entry_3(mydict, keyspec):
global cache
try:
spec = cache[keyspec]
except KeyError:
spec = tuple(keyspec.split('.'))
cache[keyspec] = spec
for key in spec:
mydict = mydict[key]
return mydict
if __name__ == "__main__":
test = get_entry(mydict, 'first.second.third.fourth')
print(test)
profile.py
from timeit import timeit
print("original get_entry")
print(timeit("get_entry(mydict, 'first.second.third.fourth')",
setup="from test import get_entry, mydict"))
print("get_entry_2 with tighter code")
print(timeit("get_entry_2(mydict, 'first.second.third.fourth')",
setup="from test import get_entry_2, mydict"))
print("get_entry_3 with cache of split spec")
print(timeit("get_entry_3(mydict, 'first.second.third.fourth')",
setup="from test import get_entry_3, mydict"))
print("just splitting a spec")
print(timeit("x.split('.')", setup="x='first.second.third.fourth'"))
我的机器上的时间是
original get_entry
4.148535753000033
get_entry_2 with tighter code
3.2986323120003362
get_entry_3 with cache of split spec
1.3073233439990872
just splitting a spec
1.0949148639992927
请注意,拆分规范对于此功能来说是一项相对昂贵的操作。这就是缓存有用的原因。
答案 2 :(得分:10)
我更新了How to use a dot "." to access members of dictionary?的答案,使用初始转换,然后对嵌套词典起作用:
您可以使用以下类来允许字典的点索引:
class dotdict(dict):
"""dot.notation access to dictionary attributes"""
__getattr__ = dict.get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
但是,如果所有嵌套字典 类型为dotdict
,则仅支持嵌套。这是以下辅助函数的来源:
def dct_to_dotdct(d):
if isinstance(d, dict):
d = dotdict({k: dct_to_dotdct(v) for k, v in d.items()})
return d
此函数必须在嵌套字典上运行一次,然后可以使用点索引对结果进行索引。
以下是一些例子:
In [13]: mydict
Out[13]: {'first': {'second': {'third': {'fourth': 'the end'}}}}
In [14]: mydict = dct_to_dotdct(mydict)
In [15]: mydict.first.second
Out[15]: {'third': {'fourth': 'the end'}}
In [16]: mydict.first.second.third.fourth
Out[16]: 'the end'
关于性能的说明:与标准字典访问相比,这个答案很慢,我只想提供一个实际使用的选项" dot access"到字典。
答案 3 :(得分:7)
这是一个类似于chrisz的解决方案,但你不需要事先做任何事情。 :
class dictDotter(dict):
def __getattr__(self,key):
val = self[key]
return val if type(val) != dict else dictDotter(val)
只有x=dictDotter(originalDict)
会让你获得任意点(`x.first.second ...)。我注意到它的速度是chrisz解决方案的两倍,而且速度是你的速度的9倍(在我的机器上,大约是)。
所以,如果你坚持做这项工作,@ tdelaney似乎提供了唯一真正的性能提升。
另一种选择比你拥有的更好(就运行时而言):
class dictObjecter:
def __init__(self,adict):
for k,v in adict.items():
self.__dict__[k] = v
if type(v) == dict: self.__dict__[k] = dictObjecter(v)
这将使你的dict中出现一个对象,所以点符号通常是正常的。这样可以将运行时间提高到你所拥有的<3> ,这也不错,但代价是要超越你的dict,并用其他东西替换它。
以下是总测试代码:
from timeit import timeit
class dictObjecter:
def __init__(self,adict):
for k,v in adict.items():
self.__dict__[k] = v
if type(v) == dict: self.__dict__[k] = dictObjecter(v)
class dictDotter(dict):
def __getattr__(self,key):
val = self[key]
return val if type(val) != dict else dictDotter(val)
def get_entry(dict, keyspec):
keys = keyspec.split('.')
result = dict[keys[0]]
for key in keys[1:]:
result = result[key]
return result
class dotdict(dict):
"""dot.notation access to dictionary attributes"""
__getattr__ = dict.get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def dct_to_dotdct(d):
if isinstance(d, dict):
d = dotdict({k: dct_to_dotdct(v) for k, v in d.items()})
return d
x = {'a':{'b':{'c':{'d':1}}}}
y = dictDotter(x)
z = dct_to_dotdct(x)
w = dictObjecter(x)
print('{:15} : {}'.format('dict dotter',timeit('y.a.b.c.d',globals=locals(),number=1000)))
print('{:15} : {}'.format('dot dict',timeit('z.a.b.c.d',globals=locals(),number=1000)))
print('{:15} : {}'.format('dict objecter',timeit('w.a.b.c.d',globals=locals(),number=1000)))
print('{:15} : {}'.format('original',timeit("get_entry(x,'a.b.c.d')",globals=locals(),number=1000)))
print('{:15} : {:.20f}'.format('best ref',timeit("x['a']['b']['c']['d']",globals=locals(),number=1000)))
我提供了最后一次常规查找作为最佳参考.Windows Ubuntu子系统上的结果:
dict dotter : 0.0035500000003594323
dot dict : 0.0017939999997906853
dict objecter : 0.00021699999979318818
original : 0.0006629999998040148
best ref : 0.00007999999979801942
因此,物化dict的速度是常规字典查找速度的3倍 - 所以如果速度很重要,为什么要这样呢?
答案 4 :(得分:2)
我有同样的需求,所以我创建了Prodict。
对于您的情况,您可以在一行中完成:
mydict = {
'first': {
'second': {
'third': {
'fourth': 'the end'
}
}
}
}
dotdict = Prodict.from_dict(mydict)
print(dotdict.first.second.third.fourth) # "the end"
之后,像dict一样使用dotdict,因为它是dict的子类:
dotdict.first == dotdict['first'] # True
您还可以使用点表示法动态添加更多键:
dotdict.new_key = 'hooray'
print(dotdict.new_key) # "hooray"
即使新密钥是嵌套字典,它仍然有效:
dotdict.it = {'just': 'works'}
print(dotdict.it.just) # "works"
最后,如果您事先定义了密钥,则会获得自动完成和自动类型转换:
class User(Prodict):
user_id: int
name: str
user = User(user_id="1", "name":"Ramazan")
type(user.user_id) # <class 'int'>
# IDE will be able to auto complete 'user_id' and 'name' properties
<强>更新强>:
这是@kabanus编写的相同代码的测试结果:
x = {'a': {'b': {'c': {'d': 1}}}}
y = dictDotter(x)
z = dct_to_dotdct(x)
w = dictObjecter(x)
p = Prodict.from_dict(x)
print('{:15} : {}'.format('dict dotter', timeit('y.a.b.c.d', globals=locals(), number=10000)))
print('{:15} : {}'.format('prodict', timeit('p.a.b.c.d', globals=locals(), number=10000)))
print('{:15} : {}'.format('dot dict', timeit('z.a.b.c.d', globals=locals(), number=10000)))
print('{:15} : {}'.format('dict objecter', timeit('w.a.b.c.d', globals=locals(), number=10000)))
print('{:15} : {}'.format('original', timeit("get_entry(x,'a.b.c.d')", globals=locals(), number=10000)))
print('{:15} : {:.20f}'.format('prodict getitem', timeit("p['a']['b']['c']['d']", globals=locals(), number=10000)))
print('{:15} : {:.20f}'.format('best ref', timeit("x['a']['b']['c']['d']", globals=locals(), number=10000)))
结果:
dict dotter : 0.04535976458466595
prodict : 0.02860781018446784
dot dict : 0.019078164088831673
dict objecter : 0.0017378700050722368
original : 0.006594238310349346
prodict getitem : 0.00510931794975705289
best ref : 0.00121740293554022105
正如您所看到的,它的表现介于&#34; dict dotter&#34;和&#34; dot dict&#34;。 任何性能增强建议将不胜感激。
答案 5 :(得分:1)
代码应该更少迭代,更具动态性!
mydict = {
'first': {
'second': {
'third': {
'fourth': 'the end'
}
}
}
}
def get_entry(dict, keyspec):
for keys in keyspec.split('.'):
dict = dict[keys]
return dict
res = get_entry(mydict, 'first.second.third.fourth')
这将花费更少的时间来执行,即使它是动态代码执行!!
答案 6 :(得分:1)
您可以在{python3中使用reduce
(functools.reduce
):
import operator
def get_entry(dct, keyspec):
return reduce(operator.getitem, keyspec.split('.'), dct)
看起来更漂亮,但性能稍差。
您的版本时间:
>>> timeit("get_entry_original(mydict, 'first.second.third.fourth')",
"from __main__ import get_entry_original, mydict", number=1000000)
0.5646841526031494
with reduce:
>>> timeit("get_entry(mydict, 'first.second.third.fourth')",
"from __main__ import get_entry, mydict")
0.6140949726104736
作为tdelaney通知 - 分裂消耗的cpu功率几乎与获取dict中的密钥一样多:
def split_keys(keyspec):
keys = keyspec.split('.')
timeit("split_keys('first.second.third.fourth')",
"from __main__ import split_keys")
0.28857898712158203
只需将字符串拆分远离get_entry
功能:
def get_entry(dct, keyspec_list):
return reduce(operator.getitem, keyspec_list, dct)
timeit("get_entry(mydict, ['first', 'second', 'third', 'fourth'])",
"from __main__ import get_entry, mydict")
0.37825703620910645