Python常见问题:“异常有多快?”

15 投票
3 回答
5604 浏览
提问于 2025-04-17 06:11

我刚刚在看Python的常见问题解答,因为在另一个问题中提到了它。之前我从来没有仔细看过,结果发现了一个问题:“异常处理有多快?”

使用try/except块是非常高效的。实际上,捕获一个异常是比较耗费资源的。在Python 2.0之前的版本中,通常会使用这样的写法:

try:
    value = mydict[key]
except KeyError:
    mydict[key] = getvalue(key)
    value = mydict[key]

我对“捕获异常是比较耗费资源的”这句话有点惊讶。这是指那些在except中真的把异常保存到一个变量里的情况,还是说所有的except情况(包括上面的例子)?

我一直认为使用这样的写法是非常符合Python风格的,特别是在Python中有一句话:“请求原谅比请求许可要容易”。而且在Stack Overflow上,很多回答通常也遵循这个想法。

捕获异常的性能真的那么差吗?在这种情况下,是否应该更倾向于“先看再跳”(LBYL)?

(注意,我并不是直接在讨论常见问题解答中的例子;还有很多其他例子是你只关注异常,而不是在之前检查类型。)

3 个回答

0

如果找不到键的情况发生得很频繁,那我建议你使用'get'方法,因为它在所有情况下都能保持稳定的速度:

s.append('''\
x = D.get('key', None)
''')

s.append('''\
x = D.get('xxx', None)
''')
6

成本当然跟具体的实现有关,但我觉得你不需要太担心这个。其实这并不会太影响你的工作。标准协议在一些奇怪的地方会抛出异常(比如说StopIteration),所以无论你愿不愿意,你都会在处理抛出和捕获异常的过程中。

在选择使用LBYL(先检查再执行)和EAFP(先执行再检查)时,应该更关注代码的可读性,而不是纠结于微小的优化。我建议尽量避免类型检查,因为这可能会降低代码的通用性。

22

捕捉异常是比较耗费资源的,但异常应该是“例外情况”(也就是说,不应该经常发生)。如果异常很少发生,那么使用 try/catch 的效率比 LBYL 更高。

下面的例子比较了在字典中查找键时,使用异常和 LBYL 的速度,分别在键存在和不存在的情况下:

import timeit

s = []

s.append('''\
try:
    x = D['key']
except KeyError:
    x = None
''')

s.append('''\
x = D['key'] if 'key' in D else None
''')

s.append('''\
try:
    x = D['xxx']
except KeyError:
    x = None
''')

s.append('''\
x = D['xxx'] if 'xxx' in D else None
''')

for i,c in enumerate(s,1):
    t = timeit.Timer(c,"D={'key':'value'}")
    print('Run',i,'=',min(t.repeat()))

输出结果

Run 1 = 0.05600167960596991       # try/catch, key exists
Run 2 = 0.08530091918578364       # LBYL, key exists (slower)
Run 3 = 0.3486251291120652        # try/catch, key doesn't exist (MUCH slower)
Run 4 = 0.050621117060586585      # LBYL, key doesn't exist

当正常情况下没有异常时,try/catch 的效率与 LBYL 相比是“极其高效”的。

撰写回答