更好地“尝试”某些东西并捕获异常或测试是否有可能首先避免异常?


139

我应该测试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说:

错误绝不能默默传递。
除非明确地保持沉默。

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


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

try:
    import foo
except ImportError:
    import baz

Answers:


161

你应该更喜欢try/exceptif/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

if index in mylist测试索引是否是mylist的元素,而不是可能的索引。您可能想要if index < len(mylist)代替。
ychaouche13年

1
这是有道理的,但是我在哪里可以找到可以使int()抛出哪些异常的文档呢?docs.python.org/3/library/functions.html#int我无法在此处找到此信息。
BrutalSimplicity

在对面,你可以添加此情况下(从关闭文档。)你的答案,当你应该更喜欢if/elsetry/catch
rappongy

20

在这种情况下,您应该完全使用其他方法:

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

不过,通常来说:如果您希望测试经常失败,请使用if。如果测试相对于尝试操作并失败则捕获异常而言代价高昂,请使用try。如果以上条件均不适用,则更容易阅读。


1
+1代表代码示例下的解释。
ktdrv 2011年

4
我认为很明显这不是他要的,他现在已经编辑了帖子以使其更加清晰。
2011年

9

使用tryexcept直接,而不是内侧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'目录时,该命令才会退出。


7

如果在进行某些操作之前先检查一下是否会失败,那么您可能应该赞成这样做。毕竟,构造异常(包括相关的回溯)需要花费时间。

异常应用于:

  1. 出乎意料的事情,或者...
  2. 您需要跳到不只一个逻辑层次的事情(例如a break不能使您走得太远),或者...
  3. 您不确切知道该如何提前处理异常的事情,或者...
  4. 提前检查故障的成本很高(相对于尝试操作而言)

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

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

不幸的if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'是,除了实际上通常比使用更快get。没有说这是最重要的标准,但这是需要注意的。
2011年

@agf:最好编写简洁明了的代码。如果以后需要优化某些东西,很容易回来重新编写,但是c2.com/cgi/wiki?PrematureOptimization
Amber

知道是什么; 我的观点是,if / else并且try / except可以有自己的位置,即使有特殊情况的替代品,因为它们具有不同的性能特征。
2011年

@agf,您是否知道get()方法在将来的版本中是否会(至少)与显式查找一样快?顺便说一句,当两次查询时(例如d中的'ABC':d ['ABC']),是否尝试:d ['ABC'],但KeyError:...不是最快?
雷米(Remi)

2
@Remi最慢的部分.get()是Python级别的属性查找和函数调用开销;在内置函数上使用关键字基本上可以解决C问题。我认为它不会很快变得越来越快。至于ifvs. try,read dict.get()方法返回一个包含一些性能信息的指针。命中与未命中的比率很重要(try如果键几乎总是存在,则可以更快),字典的大小也是如此。
2011年

5

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

  • 在try块中的代码可能有机会在捕获异常之前进行各种破坏-如果您事先使用if语句主动进行检查,则可以避免这种情况。
  • 如果在try块中调用的代码引发了一个常见的异常类型(如TypeError或ValueError),则您实际上可能没有捕获到您期望捕获的相同异常-可能是其他原因导致甚至在进入之前或之后引发相同的异常类可能引发异常的行。

例如,假设您有:

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

IndexError没有任何关于尝试获取index_list或my_list元素时是否发生的信息。


4

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

使用try表示可能会通过错误,这与使其静默通过相反。使用except导致它根本不通过。

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

警告:“错误永远都不能静默传递”,是代码可能引发您所知道的异常,并且您的设计承认存在这种可能性的情况,但您并未以处理异常的方式进行设计。在我看来,明确地消除错误将像passexcept块中那样进行,仅应在了解“不做任何事情”确实是特定情况下的正确错误处理的情况下进行操作。(这是我真正需要使用编写良好的代码进行注释的少数几次。)

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

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

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


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可行,是因为进行了数据库查询,如果x为整数,则查询将成功,如果为列表,ProgrammingError则将抛出if x

try/except在这里使用是一个不好的选择:

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

0

对于一般含义,您可以考虑阅读Python中的成语和反成语:异常

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

get(key [,默认])

如果key在字典中,则返回key的值,否则返回默认值。如果未提供default,则默认为None,因此此方法永远不会引发KeyError。


我认为该链接完全不会涵盖这种情况。它的示例与处理代表实际错误的事物有关,而不与是否针对预期情况使用异常处理有关。
2011年

第一个链接已过期。
Timofei Bondarev
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.