最好“尝试”一些东西并捕获异常或测试是不是可以首先避免异常?

Posted

技术标签:

【中文标题】最好“尝试”一些东西并捕获异常或测试是不是可以首先避免异常?【英文标题】:Better to 'try' something and catch the exception or test if it's possible first to avoid an exception?最好“尝试”一些东西并捕获异常或测试是否可以首先避免异常? 【发布时间】:2011-11-28 02:40:51 【问题描述】:

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

错误绝不应该悄无声息地过去。 除非明确静音。

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


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

try:
    import foo
except ImportError:
    import baz

【问题讨论】:

【参考方案1】:

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

【讨论】:

if index in mylist 测试索引是否是 mylist 的元素,而不是可能的索引。你会想要if index < len(mylist) 这是有道理的,但是我在哪里可以找到说明 int() 可能引发哪些异常的文档。 docs.python.org/3/library/functions.html#int我在这里找不到此信息。 相反,当您应该更喜欢if/else 而不是try/catch 时,您可以将此案例(来自off. doc)添加到您的答案中 【参考方案2】:

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

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

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

【讨论】:

+1 用于代码示例下的解释,这是正确的。 我认为这很明显不是他所要求的,他现在对帖子进行了编辑以使其更加清晰。【参考方案3】:

如果存在竞争条件的任何可能性,应该总是直接使用tryexcept 而不是在if 守卫内。例如,如果要确保目录存在,请不要这样做:

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

如果另一个线程或进程在isdirmkdir 之间创建目录,您将退出。而是这样做:

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

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

【讨论】:

【参考方案4】:

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

异常应该用于:

    出乎意料的事情,或者... 您需要跳过多个逻辑级别的事情(例如,break 不能让您走得足够远),或者... 您不知道具体什么会提前处理异常,或者... 提前检查失败的成本很高(相对于仅尝试操作而言)

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

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

【讨论】:

不幸的是,if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC' 实际上通常比使用get 更快。并不是说这是最重要的标准,但需要注意。 @agf:最好写出清晰简洁的代码。后面如果有什么需要优化的,回来改写很容易,但是c2.com/cgi/wiki?PrematureOptimization 知道;我的观点是if / elsetry / except 可以占有一席之地即使有针对特定情况的替代方案,因为它们具有不同的性能特征。 @agf,你知道 get() 方法是否会在未来的版本中改进为(至少)与显式查找一样快?顺便说一句,当查找两次时(如 if 'ABC' in d: d['ABC']),是 try: d['ABC'] except KeyError:... not faster? @Remi .get() 的缓慢部分是 Python 级别的属性查找和函数调用开销;在内置插件上使用关键字基本上直接适用于 C。我认为它不会很快变得更快。至于iftry,请阅读dict.get() method returns a pointer,其中包含一些性能信息。命中与未命中的比率很重要(如果键几乎总是存在,try 会更快)和字典的大小一样。【参考方案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 的元素时发生。

【讨论】:

【参考方案6】:

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

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

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

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

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

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

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

【讨论】:

【参考方案7】:

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

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

如果其中一个或多个问题的答案是“否”,则可能需要大量的宽恕;最有可能来自未来的自己。


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

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

程序员交谈后得知预期的控制流程是:

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

如果 x 是整数列表,则 y = bar(x)。

之所以有效,是因为foo 进行了数据库查询,如果x 是整数,则查询将成功,如果x 是列表,则抛出ProgrammingError

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

    异常名称ProgrammingError 并没有说明实际问题(x 不是整数),因此很难看出发生了什么。 ProgrammingError 在数据库调用期间引发,这会浪费时间。如果事实证明 foo 在引发异常或更改其他系统的状态之前将某些内容写入数据库,事情将变得非常糟糕。 目前尚不清楚ProgrammingError 是否仅在x 是整数列表时引发。例如,假设foo 的数据库查询中有错字。这也可能引发ProgrammingError。结果是当x 是整数时,现在也调用bar(x)。这可能会引发神秘的异常或产生不可预见的结果。 try/except 块向foo 的所有未来实现添加了要求。每当我们更改 foo 时,我们现在必须考虑它如何处理列表,并确保它抛出 ProgrammingError 而不是 AttributeError 或根本没有错误。

【讨论】:

【参考方案8】:

一般意义,你可以考虑阅读Idioms and Anti-Idioms in Python: Exceptions。

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

get(key[, 默认])

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

【讨论】:

我认为该链接根本不涵盖这种情况。它的示例是关于处理代表真正错误的事物,而不是关于是否对预期情况使用异常处理。 第一个链接已过时。

以上是关于最好“尝试”一些东西并捕获异常或测试是不是可以首先避免异常?的主要内容,如果未能解决你的问题,请参考以下文章

python动态捕获异常-乾颐堂

android 中处理崩溃异常并重启程序

检查 .NET 中的目录和文件写入权限

最好检查长度是不是超过 MAX_PATH 或捕获 PathTooLongException?

在 spark 中捕获已执行 sql 的异常

如何让 pytest 不捕获异常