150

我应该测试if某些东西是有效的还是只是try去做它并捕获异常?

  • 是否有任何可靠的文件表明首选一种方法?
  • 一种方式更pythonic吗?

例如,我应该:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

或者:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

一些想法......
PEP 20说:

错误永远不应该悄无声息地过去。
除非明确沉默。

是否应该将使用 atry而不是 anif解释为静默传递的错误?如果是这样,您是否通过以这种方式使用它来明确地使其静音,从而使其正常?


不是指您只能以一种方式做事的情况;例如:

try:
    import foo
except ImportError:
    import baz
4

8 回答 8

170

如果这会导致,你应该更try/except喜欢if/else

  • 加速(例如通过防止额外的查找)
  • 更简洁的代码(更少的行/更容易阅读)

通常,这些是齐头并进的。


加速

在尝试通过以下方式在长列表中查找元素的情况下:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

index当可能在列表中并且通常不引发 IndexError时,尝试除外是最佳选择。这样,您就无需额外查找if index < len(my_list).

Python 鼓励使用异常,您处理的异常是Dive Into Python中的一个短语。您的示例不仅(优雅地)处理异常,而不是让它静默通过,而且异常仅在未找到索引的异常情况下发生(因此出现异常一词!)。


更清洁的代码

官方 Python 文档提到EAFP请求宽恕比请求许可更容易Rob Knight指出,捕捉错误而不是避免错误,可以使代码更清晰、更易于阅读。他的例子是这样说的:

更糟糕的是(LBYL '在你跳跃之前看看')

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

更好(EAFP:请求宽恕比请求许可更容易)

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None
于 2011-09-30T00:09:27.137 回答
21

在这种特殊情况下,您应该完全使用其他东西:

x = myDict.get("ABC", "NO_ABC")

但总的来说:如果您希望测试经常失败,请使用if. 如果测试相对于仅尝试操作并在失败时捕获异常而言代价高昂,请使用try. 如果这些条件都不适用,请选择更容易阅读的内容。

于 2011-09-29T23:59:32.803 回答
9

如果存在竞争条件的任何可能性,则应始终使用tryandexcept直接而不是在if守卫内部进行。例如,如果要确保目录存在,请不要这样做:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

如果另一个线程或进程在isdir和之间创建目录mkdir,您将退出。相反,请执行以下操作:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

只有在无法创建“foo”目录时才会退出。

于 2015-09-22T20:14:18.820 回答
8

如果在你做之前检查某事是否会失败是微不足道的,你可能应该赞成这样做。毕竟,构造异常(包括它们相关的回溯)需要时间。

例外应用于:

  1. 出乎意料的事情,或者……
  2. 您需要跳过多个逻辑级别的事情(例如,abreak不能让您走得足够远),或者...
  3. 您不确切知道将提前处理异常的事情,或者......
  4. 提前检查失败的事情是昂贵的(相对于仅仅尝试操作)

请注意,通常,真正的答案是“两者都不是” - 例如,在您的第一个示例中,您真正应该做的只是使用.get()提供默认值:

x = myDict.get('ABC', 'NO_ABC')
于 2011-09-29T23:59:09.693 回答
6

正如其他帖子所提到的,这取决于情况。使用 try/except 代替预先检查数据的有效性存在一些危险,尤其是在更大的项目中使用它时。

  • try 块中的代码可能有机会在捕获异常之前造成各种破坏 - 如果您预先使用 if 语句主动检查,您可以避免这种情况。
  • 如果在 try 块中调用的代码引发了常见的异常类型,例如 TypeError 或 ValueError,那么您实际上可能没有捕获到您期望捕获的相同异常 - 它可能是在到达之前或之后引发相同异常类的其他东西可能引发异常的行。

例如,假设您有:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

IndexError 没有说明它是否在尝试获取 index_list 或 my_list 的元素时发生。

于 2014-10-16T17:03:10.060 回答
4

是否应该将使用 try 而不是 if 解释为静默传递的错误?如果是这样,您是否通过以这种方式使用它来明确地使其静音,从而使其正常?

使用try是承认错误可能会通过,这与让它静默通过相反。使用except导致它根本不通过。

在逻辑更复杂try: except:的情况下首选使用。if: else:简单胜于复杂;复杂胜于复杂;请求宽恕比请求许可更容易。

“错误永远不应该悄无声息地传递”的警告是,代码可能会引发您知道的异常,并且您的设计承认这种可能性,但您没有设计处理异常的方式。在我看来,明确地消除错误就像pass在一个except块中做一些事情,只有在理解“什么都不做”确实是特定情况下正确的错误处理的情况下才能做到这一点。(这是我觉得可能真的需要在编写良好的代码中添加注释的少数情况之一。)

但是,在您的特定示例中,两者都不合适:

x = myDict.get('ABC', 'NO_ABC')

每个人都指出这一点的原因——即使你承认你希望总体上理解,并且无法提出更好的例子——是在相当多的情况下实际上存在等效的旁路,寻找它们是解决问题的第一步。

于 2011-09-30T00:07:08.320 回答
3

每当你使用try/except控制流时,问问自己:

  1. 是否容易看出try块何时成功以及何时失败?
  2. 你知道块内的所有副作用吗?try
  3. 您是否知道块引发异常的所有情况?try
  4. 如果try块的实现发生变化,您的控制流是否仍会按预期运行?

如果对这些问题中的一个或多个问题的回答是“不”,则可能需要大量的宽恕;最有可能来自你未来的自己。


一个例子。我最近在一个更大的项目中看到了如下代码:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

与程序员交谈后发现,预期的控制流程是:

如果 x 是整数,则 y = foo(x)。

如果 x 是整数列表,请执行 y = bar(x)。

这是有效的,因为进行了数据库查询,如果是整数并且如果是列表foo,则查询将成功。xProgrammingErrorx

在这里使用try/except是一个糟糕的选择:

  1. 异常的名称ProgrammingError,并没有给出实际问题(x不是整数),这使得很难看到发生了什么。
  2. ProgrammingError数据库调用期间引发,这会浪费时间。如果事实证明foo在引发异常或更改其他系统的状态之前将某些内容写入数据库,事情将变得非常可怕。
  3. 尚不清楚是否ProgrammingError仅在x整数列表时引发。例如,假设在foo的数据库查询中有错字。这也可能引发ProgrammingError. 结果是bar(x)现在也称为 whenx是一个整数。这可能会引发神秘的异常或产生不可预见的结果。
  4. try/except块添加了对所有未来实现的要求foo。每当我们更改foo时,我们现在必须考虑它如何处理列表并确保它抛出一个ProgrammingError而不是,比如说,一个AttributeError或根本没有错误。
于 2018-08-25T07:10:10.270 回答
0

对于一般含义,您可以考虑阅读Python 中的 Idioms and Anti-Idioms: Exceptions

正如其他人所说,在您的特定情况下,您应该使用dict.get()

获取(键[,默认值])

如果键在字典中,则返回键的值,否则返回默认值。如果未给出默认值,则默认为无,因此此方法永远不会引发 KeyError。

于 2011-09-30T00:09:48.763 回答