在Python中使用'try'与'if

216 投票
9 回答
109302 浏览
提问于 2025-04-15 16:37

在决定使用 try 还是 if 来检查一个变量是否有值时,有什么考虑的理由吗?

举个例子,有一个函数可能返回一个列表,也可能什么都不返回。我想在处理这个结果之前先检查一下。下面这两种方式,哪种更好呢?为什么?

result = function();
if (result):
    for r in result:
        #process items

或者

result = function();
try:
    for r in result:
        # Process items
except TypeError:
    pass;

相关讨论:

在Python中检查成员是否存在

9 个回答

13

如果我提供的代码一开始看起来不太明显,请忽略我的解决方案,先看代码示例后再读解释。

我可以假设“没有返回值”意味着返回值是None吗?如果可以,或者如果“没有值”在布尔逻辑上是False,那么你可以这样做,因为你的代码实际上把“没有值”当作“不要进行迭代”来处理:

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

如果function()返回的不是True,你就会遍历一个空的元组,也就是说你不会进行任何迭代。这基本上就是一种“先检查后行动”的方式。

19

你的函数不应该返回混合类型的结果(比如同时返回列表和空字符串)。它应该只返回一个值的列表,或者仅仅是一个空列表。这样你就不需要进行任何测试了,也就是说,你的代码可以简化成:

for r in function():
    # process items
356

你经常会听到Python提倡一种叫做EAFP的风格(“请求原谅比请求许可更容易”),而不是LBYL风格(“跳之前先看看”)。在我看来,这主要是关于效率和可读性的问题。

在你的例子中(假设这个函数返回的是一个列表或者None,而不是返回一个列表或空字符串),如果你预计99%的情况下result会包含一些可迭代的内容,我会选择使用try/except的方式。如果异常真的很少发生,这样会更快。如果result超过50%的时间是None,那么使用if可能会更好。

为了支持这个观点,我做了一些测量:

>>> 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:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

所以,if语句总是会带来一些开销,而设置一个try/except块几乎是免费的。但是当真的发生异常时,成本就会高很多。

总结:

  • 使用try/except来控制流程是完全可以的(而且是“Python风格”的),
  • 但当异常确实是少数情况时,这种方式最有意义。

来自Python文档的说明:

EAFP

请求原谅比请求许可更容易。这种常见的Python编码风格假设有效的键或属性是存在的,并在假设错误时捕获异常。这种干净且快速的风格以大量的tryexcept语句为特征。这种技术与许多其他语言(如C语言)常用的LBYL风格形成对比。

撰写回答