为什么`尝试...除了`比`if`更快?

时间:2015-08-04 08:48:46

标签: python performance try-catch python-3.4

在我的代码中,我有一个列表l,我正在创建一个列表字典。 (我正在对具有相同key)的对象进行分组。通过try语句和if条件实现它,我在line_profiler中注意到前者看起来效率更高:

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================

   293  44378450     59805020      1.3     16.9       for element in l:

                                                        # stuff that compute 'key' from 'element'

   302   2234869      2235518      1.0      0.6              try:                                                         
   303   2234869     82486133     36.9     23.3                  d[key].append(element)                              
   304     57358        72499      1.3      0.0              except KeyError:                                             
   305     57358      1758248     30.7      0.5                  d[key] = [element]  

VS

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================

   293  44378450     60185880      1.4     14.0       for element in l:      

                                                        # stuff that compute 'key' from 'element'

   307   2234869     81683512     36.5     19.1              if key in d.keys():                                  
   308   2177511     76393595     35.1     17.8                  d.get(key).append(element)                       
   309                                                       else:                                                
   310     57358      1717679     29.9      0.4                  d[key] = [element]                               

我理解,对于try,您只会在引发异常时进入except(因此除了极少数例外情况,因此每次测试条件的总体成本更低) ,甚至Time per hit对于异常(1.3 +30.7μs)比对于测试条件(36.5μs)更慢。我认为提高异常比检查密钥是否在字典中更加昂贵(in只测试散列密钥,不是吗?它不是行搜索)。那为什么呢?

3 个答案:

答案 0 :(得分:4)

额外的运行时来自.keys()调用。如果您想阻止额外的通话,并且仍然使用ifelse,请尝试以下操作:

obj = d.get(key)
if obj:
      obj.append(element)
else:
      d[key] = [element]

或者,您可以使用defaultdict在后​​台执行此操作。例如:

from collections import defaultdict
d = defaultdict(list)
d['123'].append('abc')

答案 1 :(得分:1)

你应该认为,在每次迭代中,如果条件检查,你会花一些时间进行测试。如果你没有引发异常,你的代码会更快地尝试除外,否则需要花费更多的时间来处理异常。

换句话说,如果你确定你的异常是非常的(只在特殊情况下才会发生),你使用try-except会更便宜。否则,如果除了约50%的例外情况外,最好使用if。

(“请求宽恕比允许更容易”)

答案 2 :(得分:1)

仅当实际引发的异常数与循环执行次数相当时,

try...except才会变慢。在您的情况下,异常仅占循环迭代的2.5%。

让我们分析下面的四种情况 -

def func1():
    l = [1,2,3,4]
    d = {}
    for e in l:
        k = e - 1
        try:
            d[k].append(e)
        except KeyError:
            d[k] = [e]
    return d


def func2():
    l = [1,2,3,4]
    d = {}
    for e in l:
        k = e - 1
        if k in d.keys():
            d.get(k).append(e)
        else:
            d[k] = [e]
    return d

def func3():
    l = [1,2,3,4]
    d = {}
    for e in l:
        k = 1
        try:
            d[k].append(e)
        except KeyError:
            d[k] = [e]
    return d


def func4():
    l = [1,2,3,4]
    d = {}
    for e in l:
        k = 1
        if k in d.keys():
            d.get(k).append(e)
        else:
            d[k] = [e]
    return d

此时间结果 -

In [7]: %timeit func1()
The slowest run took 4.17 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 2.55 µs per loop

In [8]: %timeit func2()
1000000 loops, best of 3: 1.77 µs per loop

In [10]: %timeit func3()
The slowest run took 4.34 times longer than the fastest. This could mean that an intermediate result is being cached
1000000 loops, best of 3: 2.01 µs per loop

In [11]: %timeit func4()
The slowest run took 6.83 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 2.4 µs per loop
  1. func1()func2()的情况下,每个元素都会进入一个单独的列表,因此对于每个键,try..except块会引发并捕获异常。在这种情况下,func2()会更快。

  2. func3()func4()的情况下,异常只抛出一次,因此异常的开销只发生一次,而对每个密钥仍然检查条件(即使它的在这种情况下,try..except更快。

  3. 我猜你的情况可能会发生类似的情况,其中同一个密钥被多次计算,因此try..except块的速度更快。您可以查看列表中有多少实际元素与字典中有多少个键,以确定是否可能是原因。

    假设hits列是特定行执行的次数,您可以看到,行 -

    d[key].append(element)
    

    执行了2234869次,而异常只提出了 - 57358次,这只是元素总数的2.56%。