Python中异常处理的成本

142
另一个问题中,被采纳的答案建议用try/except块代替Python代码中的一个(非常简单)if语句以提高性能。
除了编码风格问题以外,并假设异常永远不会触发,相比于没有异常处理程序和使用0比较的if语句,在性能方面使用异常处理程序有多少差异?

9
你测量它时,学到了什么? - S.Lott
2
相关问题:https://dev59.com/XnI-5IYBdhLWcg3weoPR - tzot
1
如果控制流程进入except部分的可能性较小,而if/else的可能性较大,则使用try/except。 - ns15
5个回答

152

为什么不使用timeit模块来测量呢?这样你就可以看到它是否与你的应用程序相关。

好的,所以我刚刚尝试了以下操作(在Windows 11上使用Python 3.11.1):

import timeit

statements=["""\
try:
    b = 10/a
except ZeroDivisionError:
    pass""",
"""\
if a:
    b = 10/a""",
"b = 10/a"]

for a in (1,0):
    for s in statements:
        t = timeit.Timer(stmt=s, setup='a={}'.format(a))
        print("a = {}\n{}".format(a,s))
        print("%.2f usec/pass\n" % (1000000 * t.timeit(number=100000)/100000))

结果:

a = 1
try:
    b = 10/a
except ZeroDivisionError:
    pass
0.06 usec/pass

a = 1
if a:
    b = 10/a
0.05 usec/pass

a = 1
b = 10/a
0.03 usec/pass

a = 0
try:
    b = 10/a
except ZeroDivisionError:
    pass
0.27 usec/pass

a = 0
if a:
    b = 10/a
0.02 usec/pass

a = 0
b = 10/a
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
  File "C:\Python311\Lib\timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<timeit-src>", line 6, in inner
ZeroDivisionError: division by zero

正如您所见,使用 try/except 语句与显式的 if 语句之间没有太大区别,除非触发异常。 (当然,没有任何控制结构是最快的,尽管不会快多少,并且如果出现问题,它将使程序崩溃)。

将其与2010年获得的结果进行比较:

a = 1
try:
    b = 10/a
except ZeroDivisionError:
    pass
0.25 usec/pass

a = 1
if a:
    b = 10/a
0.29 usec/pass

a = 1
b = 10/a
0.22 usec/pass

a = 0
try:
    b = 10/a
except ZeroDivisionError:
    pass
0.57 usec/pass

a = 0
if a:
    b = 10/a
0.04 usec/pass

a = 0
b = 10/a
ZeroDivisionError: int division or modulo by zero

看起来我现在使用的电脑大约比我以前使用的快了两倍。处理异常的成本似乎相同,而“正常”的操作(算术)的改进甚至比控制结构的处理更多,但是那些年代的观点仍然存在:

所有这些都在同一数量级内,并且不太可能有所区别。只有在条件实际满足时(通常情况下),if版本才会显着更快。


9
有趣。因此,try/exceptif a != 0更快。 - Thilo
23
啊,用词精妙:“它们都在同一数量级内”……我怀疑许多避免使用异常的人这样做是因为他们期望异常会慢10倍。 - Garrett Bluma
1
在我的 Fedora 上使用 Python 2.7.5 运行您的代码表明,当 a=1 时,“if”版本(0.08 usec/pass)比“try/except”版本(0.11 usec/pass)更快。 - duleshi
1
@duleshi 很有趣。我想知道这是不是x86/x64的问题?或者可能是不同的处理器扩展? - Basic
1
嗯,根据定义,它们不在同一数量级内,因为“try”版本的运行时间比“if”版本慢了10倍以上。支付50微秒是否太高的代价是另一个问题。 - lesurp
1
这个答案虽然有用,但是现在已经非常陈旧了,我想知道现在的可比时间。更新结果(Dell Latitude 5410,Windows 10,Python 3.8):触发 except 块的成本现在为 0.26 微秒。所以现在的成本大约是之前的一半,在所有其他情况下,它更像是 2010 年的四分之一。 - Ben Taylor

82

实际上,这个问题已经在设计和历史常见问题中得到了回答:

如果没有引发异常,try/except块非常高效。实际上,捕获异常是代价高昂的。


8
我在想,“极其高效”到底有多高效。显然,它比一个非常简单的“if”语句还要快。 - Thilo
你发布的摘录来自于设计和历史常见问题解答 - nitsas
也许“极其高效”意味着类似于Java中所做的事情? - ebk

25

20

这个问题是误导性的。如果你假设异常永远不会被触发,那么两种代码都不是最优的。

如果你假设异常作为错误条件的一部分被触发,那么你已经超出了想要最优代码的范畴(而且你可能也不会以那种细粒度的方式处理它)。

如果你将异常作为标准控制流的一部分使用——这是Pythonic的“请求原谅,而不是事先获得许可”的方式——那么异常将被触发,成本取决于异常的类型、if的类型以及你估计异常发生的时间百分比。


7
Q: 在 Python 中,try/catch 会有显著的性能损耗么?

我在使用 try/catch 时需要担忧吗?有哪些方面需要注意吗?

这只是之前已经给出的答案的摘要。

A: 当发生异常时,if 语句比较快,否则没有区别。

@SuperNova 表示,异常处理的成本为零,因此在没有异常的情况下,与 if-语句相比更快。但是,处理异常的成本很高,因此:

对可能失败的操作使用 try。如果可能的话,避免对已知会失败的操作使用 try

例如:
  1. 好的情况下,使用 try:
try:
    x = getdata() # an external function 
except:
    print('failed. Retrying')
  1. 糟糕的情况下,这里更倾向于使用if版本:
y = f(x) # f never fails but often returns 0
try:
    z = 1 / y # this fails often
except:
    print('failed.')

# if-version
y = f(x)
if y != 0:
    z = 1 / y
else:
    print('failed.')


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接