在python中使用try vs if

Using try vs if in python

在测试变量是否具有值时,是否有理由决定使用哪个tryif构造?

例如,有一个函数返回列表或不返回值。我想在处理之前检查一下结果。以下哪一项更可取?为什么?

1
2
3
4
result = function();
if (result):
    for r in result:
        #process items

1
2
3
4
5
6
result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

相关讨论:

检查python中是否存在成员


你经常听说python鼓励eafp风格("请求宽恕比允许更容易")而不是lbyl风格("三思而后行")。对我来说,这是一个效率和可读性的问题。

在您的示例中(假设函数不是返回一个列表或空字符串,而是返回一个列表或None),如果您希望99%的时间result实际上包含一些不可重复的内容,那么我将使用try/except方法。如果异常真的是异常的,那么速度会更快。如果resultNone的50%以上,那么使用if可能更好。

要通过一些测量来支持这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:
 a/b
except ZeroDivisionError:
 pass"
)
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:
 a/b
except ZeroDivisionError:
 pass"
)
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:
 a/b"
)
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:
 a/b"
)
0.051202772912802175

所以,虽然一个if声明总是要花费你的钱,但是建立一个try/except块几乎是免费的。但是当一个Exception实际发生时,成本要高得多。

寓意:

  • 使用try/except进行流量控制是完全正常的(和"Python式的"),
  • 但当Exception实际上是例外时,这是最有意义的。

从python文档中:

EAFP

Easier to ask for forgiveness than
permission. This common Python coding
style assumes the existence of valid
keys or attributes and catches
exceptions if the assumption proves
false. This clean and fast style is
characterized by the presence of many
try and except statements. The
technique contrasts with the LBYL
style common to many other languages
such as C.


函数不应返回混合类型(即列表或空字符串)。它应该返回一个值列表或者只是一个空列表。然后您就不需要测试任何东西,即您的代码折叠为:

1
2
for r in function():
    # process items


如果我提供的代码乍一看不明显,您必须在代码示例之后阅读解释,请忽略我的解决方案。

我可以假设"无返回值"表示返回值为"无"?如果是,或者如果"no value"是假布尔值,则可以执行以下操作,因为您的代码本质上将"no value"视为"do not iterate":

1
2
for r in function() or ():
    # process items

如果function()返回了不正确的内容,那么您将迭代空元组,即不运行任何迭代。这基本上是lbyl。


第二个示例被破坏了——代码永远不会抛出类型错误异常,因为您可以同时遍历字符串和列表。遍历空字符串或列表也是有效的-它将执行循环体零次。


Which of the following would be more preferable and why?

在这种情况下,最好是三思而后行。使用异常方法,类型错误可能发生在循环体的任何地方,它会被捕获并丢弃,这不是您想要的,并且会使调试变得棘手。

(不过,我同意布兰登·考夫曼的观点:不返回"无项目"而不返回空列表是不可能的。在Python中不应该看到Java编译器的一个令人不快的习惯。或者Java。


一般来说,我的印象是例外情况应该保留在例外情况下。如果预计result永远不会是空的(但可能是,例如,磁盘崩溃等),那么第二种方法是有意义的。另一方面,如果一个空的result在正常情况下是完全合理的,那么用if语句进行测试就更有意义了。

我想到了(更常见的)情景:

1
2
3
4
5
6
7
# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

代替等效物:

1
2
3
4
5
...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1


Bobince明智地指出,包装第二个案例也可以在循环中捕获类型错误,这不是您想要的。如果你真的想使用一个尝试,你可以在循环之前测试它是否是可测试的。

1
2
3
4
5
6
7
8
result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

如你所见,它相当难看。我不建议这样做,但为了完整起见,应该提及它。


就性能而言,对通常不引发异常的速度比每次使用if语句都快。因此,决定取决于例外情况的概率。


一般来说,您不应该使用Try/Catch或任何异常处理工具来控制流。尽管后台迭代是通过引发StopIteration异常来控制的,但是您还是应该更喜欢第一个代码片段而不是第二个代码片段。