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

Mik*_*aig 15 python exception

在一些地方(这里这里)已经说过,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
Run Code Online (Sandbox Code Playgroud)

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

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

Ale*_*lli 30

只应在真正例外情况下调用异常

不是在Python中:例如,每个 for循环(除非过早地breaks或returns StopIteration)都会被抛出并捕获的异常()终止.因此,每个循环发生一次的异常对Python来说并不陌生 - 它经常出现在那里!

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

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


Gle*_*ard 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")
Run Code Online (Sandbox Code Playgroud)

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


Jon*_*age 7

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

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
Run Code Online (Sandbox Code Playgroud)

..从而避免尝试 - 除外.

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

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

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