我有一些包含类似数据的词典。
大多数查询都可以通过一次搜索来解决。
因此,在dict中对一个键的存在进行初步检查并在下一个dict中尝试捕获键错误时,是否更好的性能呢?
或者类似
# d1, d2, d3 = bunch of dictionaries
value = d1.get(key, d2.get(key, d3.get(key, 0)))
答案 0 :(得分:4)
取决于字典中的键。
如果您有信心地预测密钥丢失会更常见,请使用get。
如果你有信心地预测钥匙更常见,请使用try除外。
答案 1 :(得分:4)
似乎在几乎所有情况下,使用get
会更快。这是我使用try..except
进行的测试并获取
>>> def foo1(n):
spam = dict(zip(range(-99,100,n),[1]*200))
s = 0
for e in range(1,100):
try:
s += spam[e]
except KeyError:
try:
s += spam[-e]
except KeyError:
s += 0
return s
>>> def foo2(n):
spam = dict(zip(range(-99,100,n),[1]*200))
s = 0
for e in range(1,100):
s += spam.get(e, spam.get(-e,0))
return s
>>> for i in range(1,201,10):
res1 = timeit.timeit('foo1({})'.format(i), setup = "from __main__ import foo1", number=1000)
res2 = timeit.timeit('foo2({})'.format(i), setup = "from __main__ import foo2", number=1000)
print "{:^5}{:10.5}{:10.5}{:^10}{:^10}".format(i,res1,res2,foo1(i),foo2(i))
1 0.075102 0.082862 99 99
11 0.25096 0.054272 9 9
21 0.2885 0.051398 10 10
31 0.26211 0.060171 7 7
41 0.26653 0.053595 5 5
51 0.2609 0.052511 4 4
61 0.2686 0.052792 4 4
71 0.26645 0.049901 3 3
81 0.26351 0.051275 3 3
91 0.26939 0.051192 3 3
101 0.264 0.049924 2 2
111 0.2648 0.049875 2 2
121 0.26644 0.049151 2 2
131 0.26417 0.048806 2 2
141 0.26418 0.050543 2 2
151 0.26585 0.049787 2 2
161 0.26663 0.051136 2 2
171 0.26549 0.048601 2 2
181 0.26425 0.050964 2 2
191 0.2648 0.048734 2 2
>>>
答案 2 :(得分:1)
由于您说大多数查询都是通过查看第一个dict来解决的,因此您的最快解决方案将执行以下操作:
try:
item = d1[key]
except KeyError:
try:
item = d2[key]
except KeyError:
...
然而,这肯定不是最易维护的解决方案,我不建议使用它。你可以创建一个函数:
def get_from(item,dicts):
for d in dicts:
try:
return d[item]
except KeyError:
pass
else:
raise KeyError("No item in dicts")
您可以这样称呼:
get_from(key,(d1,d2,d3))
(这是@MartijnPieters在原始问题的评论中提出的已经非常简单的Chained地图配方的简化,稍微不那么干净的版本 - 我会主张在这里发布的代码上使用它。这段代码只是以更简化的方式演示概念。)
最后,也许混合解决方案在实践中效果最好。将第一个try
计算在循环之外 - 这有点难看,但它在大多数情况下避免了loop
的开销。只有当第一个try
引发KeyError
时,才会输入我在上面提到的剩余dicts上的循环类型解决方案。 e.g:
try:
item = d1[key]
except KeyError:
item = get_from(key,(d2,d3))
再次 ,只有在你可以可靠地证明(认为timeit
)它会产生可衡量的差异时才会这样做
要知道的重要一点是,在python中,try
很便宜,但except
花费了相当多的时间。如果您的代码预计会成功,请使用try
- except
。如果预计不会成功,通常最好使用try-except
,但在这种情况下,您应该评估性能是否真的是一个问题,并且只有当您能够证明它是一个问题时才应该使用“在你跳跃之前看。”
最后要注意的是,如果词典相对静止,可能值得将它们合并为1 dict
:
d1.update(d2)
d1.update(d3)
现在您可以使用d1
- 它包含d2
和d3
的所有信息。 (当然,如果dicts具有相同但具有不同值的键,则更新的顺序很重要。)
答案 3 :(得分:1)
try...except
通常需要比使用get
更长的时间,但这取决于一些事情......
尝试使用timeit模块在特定情况下测试性能,如下所示:
def do_stuff():
blah
timeit.timeit('testfunc()', 'from __main__ import do_stuff as testfunc')
答案 4 :(得分:0)
你也可以这样做
sentinel = object()
values = (d.get(key, sentinel) for d in (d1, d2, d3))
value = next(v for v in values if v is not sentinel)
如果没有任何一个词组包含该键,则会引发StopIteration
而不是KeyError
。
答案 5 :(得分:0)
检查条件
之间的区别 if 'key' in a_dict
或类似地,
if a_dct.get('key') == None
并且处理KeyError
时not 'key' in a_dict
被抛出通常被认为是微不足道的,并且可能取决于您正在使用的python的实现。
使用条件形式无疑是更加pythonic,并且通常被认为比捕获异常更具表现力,通常导致更清晰的代码。但是,如果您的字典可能包含任意数据,并且您无法知道值None
或某个其他魔术值表示未找到您的密钥,则使用条件表单将需要两次查找,因为您首先检查如果密钥在字典中,然后检索该值。即:
if 'key': in a_dict:
val = a_dcit['key']
根据您描述的情况,您提供的代码是最慢的选项,因为key
将在每个词典中查找。更快的选择是猜测它将进入的字典,并依次搜索其他字典:
my_val = d1.get(key,None)
if my_val == None:
my_val = d2.get(key,None)
if my_val == None:
my_val = d3.get(key,None)
if my_val == None:
return False #handle not found in any case
然而,您的特定用例听起来有趣且奇怪。为什么有多个具有类似数据的词典?这些词典是如何存储的?如果您已经有一个列表或其他一些包含这些词典的数据结构,那么循环遍历词典会更具表现力。
dict_list = [{},{},{}] #pretend you have three dicts in a list
for d in dict_list:
val = d.get('key',None)
if val == None:
break
#val is now either None, or found.