我应该测试 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
如果这会导致您更喜欢 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
在这种特殊情况下,您应该完全使用其他东西:
x = myDict.get("ABC", "NO_ABC")
不过,一般来说:如果您预计测试会经常失败,请使用 if
。如果测试相对于仅尝试操作并在失败时捕获异常而言代价高昂,请使用 try
。如果这些条件都不适用,请选择更容易阅读的内容。
如果存在竞争条件的任何可能性,应该始终直接使用 try
和 except
而不是在 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”目录时才会退出。
如果在你做之前检查某事是否会失败是微不足道的,你可能应该赞成这样做。毕竟,构造异常(包括它们相关的回溯)需要时间。
例外应用于:
出乎意料的事情,或者……你需要跳过一个以上逻辑层次的事情(例如,休息不能让你走得足够远),或者……你不知道到底发生了什么的事情提前处理异常,或者......提前检查失败的事情是昂贵的(相对于只是尝试操作)
请注意,真正的答案通常是“两者都不是” - 例如,在您的第一个示例中,您真正应该 做的只是使用 .get()
来提供默认值:
x = myDict.get('ABC', 'NO_ABC')
if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'
实际上通常比使用 get
快。并不是说这是最重要的标准,但需要注意这一点。
if / else
和 try / except
即使有针对特定案例的替代方案也可以占有一席之地,因为它们具有不同的性能特征。
.get()
的缓慢部分是 Python 级别的属性查找和函数调用开销;在内置插件上使用关键字基本上直接适用于 C。我认为它不会很快变得更快。至于 if
与 try
,请阅读包含一些性能信息的 dict.get() method returns a pointer。命中与未命中的比率很重要(如果键几乎总是存在,try
会更快),字典的大小也很重要。
正如其他帖子所提到的,这取决于情况。使用 try/except 代替预先检查数据的有效性存在一些危险,尤其是在更大的项目中使用它时。
try 块中的代码可能有机会在捕获异常之前造成各种破坏 - 如果您预先使用 if 语句主动检查,您可以避免这种情况。
如果在 try 块中调用的代码引发了一个常见的异常类型,例如 TypeError 或 ValueError,那么您实际上可能没有捕获到您期望捕获的相同异常 - 它可能是在到达之前或之后引发相同异常类的其他东西可能引发异常的行。
例如,假设您有:
try:
x = my_list[index_list[3]]
except IndexError:
x = 'NO_ABC'
IndexError 没有说明它是否在尝试获取 index_list 或 my_list 的元素时发生。
是否应该将使用 try 而不是 if 解释为静默传递的错误?如果是这样,您是否通过以这种方式使用它来明确地使其静音,从而使其正常?
使用 try
是承认错误可能会通过,这与让它静默通过相反。使用 except
导致它根本不通过。
在 if: else:
逻辑更复杂的情况下,首选使用 try: except:
。简单胜于复杂;复杂胜于复杂;请求宽恕比请求许可更容易。
“错误永远不应该悄无声息地传递”的警告是,代码可能引发您知道的异常,并且您的设计承认这种可能性,但您没有设计处理异常的方式。在我看来,明确地消除错误会在 except
块中执行类似 pass
的操作,只有在理解“什么都不做”确实是特定情况下正确的错误处理的情况下才应该这样做。 (这是我觉得可能真的需要在编写良好的代码中添加注释的少数情况之一。)
但是,在您的特定示例中,两者都不合适:
x = myDict.get('ABC', 'NO_ABC')
每个人都指出这一点的原因——即使你承认你希望总体上理解,并且无法提出更好的例子——是在相当多的情况下实际上存在等效的旁路,寻找它们是解决问题的第一步。
每当您使用 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 或根本没有错误。
对于一般含义,您可以考虑阅读 Idioms and Anti-Idioms in Python: Exceptions。
正如其他人所说,在您的特定情况下,您应该使用 dict.get()
:
get(key[, default]) 如果 key 在字典中,则返回 key 的值,否则返回默认值。如果未给出默认值,则默认为无,因此此方法永远不会引发 KeyError。
不定期副业成功案例分享
if index in mylist
测试索引是否是 mylist 的元素,而不是可能的索引。您可能需要if index < len(mylist)
。if/else
而不是try/catch
时,您可以将这种情况(来自 off. doc)添加到您的答案中