检查是否在dict或try / except中哪些在python中有更好的性能?

时间:2012-12-13 15:02:06

标签: python dictionary

我有一些包含类似数据的词典。

大多数查询都可以通过一次搜索来解决。

因此,在dict中对一个键的存在进行初步检查并在下一个dict中尝试捕获键错误时,是否更好的性能呢?

或者类似

# d1, d2, d3 = bunch of dictionaries

value = d1.get(key, d2.get(key, d3.get(key, 0)))

6 个答案:

答案 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 - 它包含d2d3的所有信息。 (当然,如果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

并且处理KeyErrornot '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.