最好“尝试”某事并捕获异常或测试是否有可能先避免异常?
- 2025-01-22 08:45:00
- admin 原创
- 69
问题描述:
我应该测试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
而不是 an是否应if
被解释为错误悄悄传递?如果是这样,您是否通过这种方式明确地将其静音,从而使其正常?
我指的并不是只能用一种方式做事的情况;例如:
try:
import foo
except ImportError:
import baz
解决方案 1:
如果try/except
结果为if/else
加速(例如通过防止额外的查找)
更简洁的代码(更少的行数/更易读)
通常,这些是相辅相成的。
加速
尝试通过以下方式在长列表中查找元素:
try:
x = my_list[index]
except IndexError:
x = 'NO_ABC'
当 可能在列表中并且通常不会引发 IndexError 时,try, except 是最佳选择index
。这样,您就无需通过 进行额外查找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
解决方案 2:
在这种特殊情况下,你应该使用完全不同的方法:
x = myDict.get("ABC", "NO_ABC")
但一般来说:如果您预计测试会频繁失败,请使用if
。如果测试相对于尝试操作并在失败时捕获异常而言成本较高,请使用try
。如果这两个条件都不适用,请选择更容易理解的。
解决方案 3:
如果存在任何竞争条件的可能性,则应始终直接使用try
andexcept
而不是在保护内部使用。例如,如果您想确保目录存在,请不要这样做: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”目录时才会退出。
解决方案 4:
如果在执行某项操作之前检查它是否会失败很简单,那么您可能应该支持这样做。毕竟,构造异常(包括其相关的回溯)需要时间。
以下情况应使用例外:
意想不到的事情,或者……
您需要跳过多个逻辑层次的事情(例如,当 a
break
不能让您走得足够远时),或者......您无法提前知道具体要处理什么异常,或者......
提前检查失败的成本很高(相对于尝试操作而言)
请注意,很多时候,真正的答案是“都不是” - 例如,在您的第一个例子中,您真正应该做的只是使用.get()
来提供默认值:
x = myDict.get('ABC', 'NO_ABC')
解决方案 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
表示根本不让错误通过。
try: except:
在逻辑比较复杂的情况下,优先使用if: else:
。简单比复杂好;复杂比复杂好;请求原谅比请求许可更容易。
“错误绝不应默默传递”所警告的情况是,代码可能会引发您知道的异常,并且您的设计承认这种可能性,但您没有设计出处理异常的方法。在我看来,明确地抑制错误就像pass
在块中执行某些操作except
,只有在理解“什么都不做”才是特定情况下正确的错误处理的情况下才能这样做。(这是我觉得在编写良好的代码中可能确实需要注释的少数几次之一。)
但是,在您的特定示例中,两者都不合适:
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
`ProgrammingError`x
在这里使用try/except
是一个糟糕的选择:
异常的名称
ProgrammingError
并未透露实际问题(不是x
整数),这使得很难看出发生了什么。在数据库调用期间引发,这很浪费时间。如果在数据库引发异常之前写入某些内容,或者更改其他系统的状态,
ProgrammingError
事情就会变得非常糟糕。foo
目前尚不清楚是否
ProgrammingError
仅当x
是整数列表时才会引发。例如,假设foo
的数据库查询中有一个拼写错误。这也可能引发ProgrammingError
。结果是bar(x)
现在当 是整数时也会调用x
。这可能会引发神秘的异常或产生不可预见的结果。该
try/except
块为 的所有未来实现添加了一项要求foo
。每当我们更改 时foo
,我们现在都必须考虑它如何处理列表,并确保它抛出ProgrammingError
而不是AttributeError
或根本没有错误。
解决方案 8:
要了解一般含义,您可以考虑阅读《Python 中的习语和反习语:异常》。
在您的特定情况下,正如其他人所述,您应该使用dict.get()
:
获取(键[,默认])
如果 key 在字典中,则返回 key 的值,否则返回默认值。如果没有给出默认值,则默认为 None,这样此方法就不会引发 KeyError。