我有一些包含类似数据的字典。
大多数查询将在一个字典的一次搜索中得到解决。
那么,省略对字典中是否存在键的初步检查并尝试在异常子句中捕获键错误的下一个字典是否更好?
或者也许像
# d1, d2, d3 = bunch of dictionaries
value = d1.get(key, d2.get(key, d3.get(key, 0)))
?
我有一些包含类似数据的字典。
大多数查询将在一个字典的一次搜索中得到解决。
那么,省略对字典中是否存在键的初步检查并尝试在异常子句中捕获键错误的下一个字典是否更好?
或者也许像
# d1, d2, d3 = bunch of dictionaries
value = d1.get(key, d2.get(key, d3.get(key, 0)))
?
似乎在几乎所有情况下,使用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
>>>
取决于字典中的键。
如果您有信心预测缺少使用键的情况更为常见,请使用 get。
如果您有信心预测键更常见,请使用 try except。
try...except
通常比使用需要更长的时间,get
但这取决于一些事情......
尝试使用 timeit 模块在您的特定情况下测试性能,如下所示:
def do_stuff():
blah
timeit.timeit('testfunc()', 'from __main__ import do_stuff as testfunc')
由于您说大多数查询将通过查看第一个字典来解决,因此您最快的解决方案是执行以下操作:
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 在对原始问题的评论中建议的已经非常简单的链式地图配方的简化版本,稍微不那么干净——我建议在此处发布的代码中使用它。此代码仅用于演示更简化的概念。)
最后,也许混合解决方案在实践中效果最好。将第一个因素try
排除在循环之外——这有点难看,但它避免了loop
大部分时间的开销。只有当第一个try
提出 aKeyError
时,您才会输入我在上面建议的其余 dicts 的循环类型解决方案。例如:
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
。(当然,如果字典具有相同但具有不同值的键,则更新的顺序很重要)。
你也可以
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)
如果没有任何字典包含密钥,则会引发 aStopIteration
而不是 a KeyError
。
检查条件的区别
if 'key' in a_dict
或类似地,
if a_dct.get('key') == None
和处理KeyError
抛出的时间not 'key' in a_dict
通常被认为是微不足道的,并且可能取决于您正在使用的 python 的实现。
使用条件形式无疑更符合 Python 风格,通常被认为比捕获异常更具表现力,通常会导致代码更简洁。但是,如果您的字典可能包含任意数据,并且您不知道 的值None
或其他一些神奇值表示您的键未找到,则使用条件形式将需要两次查找,因为您首先检查键是否在字典,然后检索值。IE:
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.