Python例外:EAFP和什么是真正的例外?

时间:2010-06-21 17:13:49

标签: python exception

在一些地方(herehere)已经说过,Python强调“请求宽恕比允许更容易”(EAFP)应该通过以下观点进行调整:在真正特殊情况下被召唤。请考虑以下内容,我们将弹出并推送优先级队列,直到只剩下一个元素:

import heapq
...
pq = a_list[:]
heapq.heapify(pq)
while True:
    min1 = heapq.heappop(pq)
    try:
        min2 = heapq.heappop(pq)
    except IndexError:
        break
    else
        heapq.heappush(pq, min1 + min2)
# do something with min1

异常仅在循环的len(a_list)次迭代中引发一次,但它并不是特别的,因为我们知道它最终会发生。这个设置使我们无法检查a_list是否空了很多次,但是(可能)它比使用显式条件更不易读。

对这种非特殊程序逻辑使用异常的共识是什么?

5 个答案:

答案 0 :(得分:30)

  

只应调用异常   真正特殊的案例

不在Python中:例如,每个 for循环(除非它过早breakreturn s)以异常终止({{1被抛出并被抓住了。因此,每个循环发生一次异常对Python来说并不陌生 - 它经常出现在那里!

有问题的原则在其他语言中可能至关重要,但这绝对没有理由将该原则应用于Python,因为它与语言的精神背道而驰。

在这种情况下,我喜欢Jon的重写(应该通过删除else分支进一步简化)因为它使代码更紧凑 - 一个实用的原因,绝对不是具有外来原则的Python风格的“调和”。

答案 1 :(得分:9)

在大多数低级语言(如C ++)中抛出异常代价很高。这影响了许多关于异常的“常识”,并不适用于在VM中运行的语言,如Python。 Python中使用异常而不是条件的成本并不高。

(这是“常识”成为习惯问题的一种情况。人们从一种环境中的经验 - 低级语言 - 来到它,然后将其应用到新的领域而不评估它是否有道理。)

总的来说,例外仍然是例外。这并不意味着它们不经常发生;这意味着它们是例外。它们往往会破坏普通代码流,而且大多数时候你不想逐个处理 - 这是异常处理程序的重点。这部分在Python中与在C ++和所有其他语言中相同。

但是,这往往会定义何时抛出异常。你在谈论异常应该被抓住。非常简单,不要担心:异常并不昂贵,所以不要竭尽全力防止它们被抛出。围绕这个设计了很多Python代码。

我不同意Jon的建议,试图提前测试并避免异常。如果它导致更清晰的代码,那就没问题,就像在他的例子中那样。但是,在许多情况下,它只会使事情变得复杂 - 它可以有效地导致重复检查和引入错误。例如,

import os, errno, stat

def read_file(fn):
    """
    Read a file and return its contents.  If the file doesn't exist or
    can't be read, return "".
    """
    try:
        return open(fn).read()
    except IOError, e:
        return ""

def read_file_2(fn):
    """
    Read a file and return its contents.  If the file doesn't exist or
    can't be read, return "".
    """
    if not os.access(fn, os.R_OK):
        return ""
    st = os.stat(fn)
    if stat.S_ISDIR(st.st_mode):
        return ""
    return open(fn).read()

print read_file("x")

当然,我们可以测试并避免失败 - 但我们的事情很复杂。我们试图猜测文件访问可能失败的所有方式(这并没有捕获所有这些),我们可能已经引入了竞争条件,而且我们正在做更多的I / O工作。这一切都是为我们完成的 - 只是抓住了例外。

答案 2 :(得分:7)

查看the docs我认为您可以安全地重写函数,如下所示:

import heapq
...
pq = heapq.heapify(a_list)
while pq:
    min1 = heapq.heappop(pq)
    if pq:
        min2 = heapq.heappop(pq)
        heapq.heappush(pq, min1 + min2)
# do something with min1

..从而避免了try-except。

到达列表的末尾,这是你知道的将要发生的事情不是例外 - 它是非常的!因此,更好的做法是提前处理它。如果你在另一个线程中有其他东西在同一堆中消耗,那么使用try-except会更有意义(即处理特殊/不可预测的情况)。

更一般地说,无论我在哪里测试,我都会避免尝试 - 并提前避免失败。这迫使你说“我知道这种糟糕的情况可能会发生,所以这就是我如何处理它”。在我看来,你会倾向于编写更易读的代码。

[编辑] 根据Alex的建议更新了示例

答案 3 :(得分:4)

只是为了记录,我写的是这样的:

import heapq
a_list = range(20)
pq = a_list[:]
heapq.heapify(pq)
try:
    while True:
        min1 = heapq.heappop(pq)
        min2 = heapq.heappop(pq)
        heapq.heappush(pq, min1 + min2)
except IndexError:
    pass # we ran out of numbers in pq

异常可以留下一个循环(甚至函数),你可以使用它们。由于Python将它们抛到各处,我认为这种模式非常有用(甚至 pythonic )。

答案 4 :(得分:3)

我发现使用异常作为“普通”流控制工具的做法在Python中被广泛接受。它最常用于你描述的情况,当你到达某种序列的末尾时。

在我看来,这是对异常的完全有效使用。不过,你确实要小心使用异常处理。引发异常是一项相当昂贵的操作,因此最好确保在序列结束时只依赖异常,而不是每次迭代。