Python 异常 - 原力的黑暗面
最近一篇博文 如果你不喜欢异常,那么你并不喜欢 Python 来回出现,并迫使我写一个部分反驳。并不是说那篇博文完全错误,只是它并不是这个主题的要义。而如果要我补充的话,它有点武断。
原文声称,异常是 Python 的核心,“异常应该只用于错误,而不是正常的流程控制”的常见建议是错误的,接着解释说,异常被用于核心实现,例如迭代器协议,以及属性访问,因此,它们是该语言的核心特征。博文的一些较长部分是关于揭穿 Java 和 C++程序员的常见误解。
粗略来讲,那篇文章中的异常被描述成非常有用的,并且极具盛赞,以至于所有关于它们的使用的批评和问题都黯然失色。
使用异常来处理错误
这是我打心里同意 barnert 的一点。错误应该使用异常来传播,所以
def min(*lst):
if not lst:
raise ValueError("min(..) requires a list with at least one element")
minimum = lst[0]
for item in lst:
if minimum > item:
minimum = item
return item
是异常的一个完全正确的使用,如果调用者的代码不能保证参数是长度大于 0 的列表,那么它必须检查这些异常。
异常与值和变量是分离的
有时,我被使用像这样的模式的代码呛到:
result = []
result.append(dosomething(bar))
try:
foo = bar[key][anotherkey]
res = dosomething(foo)
result.append(res[evenanotherkey])
except KeyError:
....
finally:
return result
这段代码有许多异常相关的问题,并且展示了如何不使用异常。首先,并不清楚 try 块中那一个键访问会引发异常。它可能是 bar[key]
, 或者是 _[anotherkey]
, 也有可能是 res[evenanotherkey]
, 或者最后可能发生在 dosomething(foo)
。该异常机制将错误处理与值和变量分离。我的问题是:你能辨别出是否是想要从 dosomething()
中捕获 KeyErrors 吗?
因此,在使用异常时,对于捕获哪些异常以及不捕获哪些异常,人们必须非常小心。 使用防御性编程式(例如, haskey()
)的检查,它是明确的,并几乎与为每一个索引操作写出单独的 try-catch
块一样对代码是“侵入性的”。
异常风险
所以,使用异常时,基本上有两种风险:
- 应该捕获的异常没捕获
- 错误的捕获异常
第一个风险当然是一个风险,但对它,我并不过于担心。第二个是我非常害怕的一个风险。你的代码中有多少函数能够抛出 eyErrors, ValueError, IndexError, TypeError, 和 RuntimeError 异常呢?
作为 Python 化的 goto 的异常
异常可以模拟 goto
语句。当然,它们不仅跳转到堆栈的顶级,而且也跳转进语句中。在 C 代码中,goto 是函数局部控制流和错误处理(对错误处理而言,他们更无争议)的一个主要工具:
int
max_in_two_dim(double * array, size_t N, size_t M, double *out) {
if (N * M == 0)
goto empty_array_lbl;
double max = array[0];
for (int i=0; i < N; ++i) {
for (int j=0; j < M; ++j) {
double val = array[j * N +k];
if (val != val) // NaN case
goto err_lbl;
if (max < val)
max = val;
}
}
return 0;
nan_lbl:
fprintf(stderr, "encountered a not-a-number value when unexpected");
return -1;
empty_array_lbl:
fprintf(stderr, "no data in array with given dims");
return -2;
}
你可以使用 Python 中的异常来模拟这种用法。我已经见过大量这种代码:
def whatever(arg1, arg2):
try:
for i in range(N):
for j in range(M):
# ..
if ...:
raise RuntimeError("jump")
return out
except RuntimeError:
# cleanup
# ..
在大多数情况下,有更好的办法来避免这种模式。Python 的 for
循环有一个可选的 else
分支,用来帮助避免这种跳转。然而,在循环等其他一些地方上,这种模式会出现 RuntimeError
错误。
Meta: 局内人与局外人的思考
我最不喜欢 barnert 的文章的地方几乎可能是可以才能够标题:“如果……,那么你并不喜欢 Python”中读到的东西。这与我所听到的很多关于代码/软件/解决方法是“Pythonic”的并驾齐驱。它似乎暗示着必须站队:你是与正统的 Python 社区站在一边呢,还是一个局外人,即那些不够“Pythonic”的人。所有这些都对改善代码毫无帮助。
总结
异常时 Python 中一个中心且强大的工具。但请小心谨慎的使用它们。不要假装它们像一个魔术棒,也不要为了显示你对 Python 的爱来使用它们。在要求异常使用的单一情景下使用它们。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: 在 Python 3 中比较类型
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论