Python Exceptions: EAFP and What is Really Exceptional?
有人在一些地方(这里和这里)说,python强调"请求宽恕比请求许可更容易"(eafp)的观点应该与这样的观点相调和,即只有在真正特殊的情况下才应该调用异常。考虑以下情况,我们将弹出并推送一个优先级队列,直到只剩下一个元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
例外只在EDOCX1的0次迭代循环中被提升一次,但它并不是真正的例外,因为我们知道它最终会发生。这个设置使我们不必检查EDCOX1 1是否是空的,但是(也许)它比使用显式条件更不可读。
对于这种非异常程序逻辑使用异常有什么共识?
exceptions should only be called in
truly exceptional cases
不在python中:例如,每个
所讨论的原则在其他语言中可能是至关重要的,但这绝对没有理由将该原则应用于Python,因为它与语言的精神非常相反。
在本例中,我喜欢jon的rewrite(应该通过删除else分支进一步简化),因为它使代码更加紧凑——这是一个实用的原因,而不是用一个异类原则来"回火"python风格。
在大多数低级语言(如C++)中,抛出异常是昂贵的。这影响了很多关于异常的"常识",并且对运行在VM中的语言(如Python)也没有太大影响。在Python中,使用异常而不是条件并没有那么大的开销。
(在这种情况下,"常识"变成了习惯。人们从一种类型的环境(低级语言)的经验中获得它,然后将它应用到新的领域,而不评估它是否有意义。)
一般来说,例外仍然是例外。这并不意味着它们不会经常发生;这意味着它们是例外。它们往往会从普通的代码流中分离出来,而且在大多数情况下,您不想一个接一个地处理它们——这就是异常处理程序的要点。在Python中,这一部分与C++和所有其他异常的语言都是相同的。
然而,这往往会定义何时抛出异常。你说的是什么时候应该抓住例外。很简单,不要担心:异常并不昂贵,所以不要想方设法阻止抛出异常。很多python代码都是围绕这个设计的。
我不同意乔恩的建议,即尝试提前测试并避免出现异常。如果它导致更清晰的代码,就像在他的例子中那样,那就好了。然而,在很多情况下,这只会使事情变得复杂——它可以有效地导致重复检查和引入错误。例如,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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工作。这都是为我们做的--抓住例外。
查看文档,我认为您可以安全地重新编写以下函数:
1 2 3 4 5 6 7 8 9 | 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,但这样做会更有意义(即处理特殊/不可预测的情况)。
更一般地说,我会避免在任何可以测试的地方尝试例外,并避免提前失败。这迫使你说,"我知道这种糟糕的情况可能会发生,所以下面是我如何处理它"。在我看来,这样做的结果是,您将倾向于编写更可读的代码。
[编辑]根据Alex的建议更新示例
就为了记录,我会这样写:
1 2 3 4 5 6 7 8 9 10 11 | 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把它们扔到了任何地方,所以我认为这个模式非常有用(甚至是Python)。
我发现使用异常作为"普通"流控制工具的实践在Python中被广泛接受。当你到达某个序列的末尾时,它最常用于你描述的那种情况。
在我看来,这是一个完全正确的例外使用。不过,您确实希望谨慎地随意使用异常处理。引发异常是一个相当昂贵的操作,因此最好确保您只依赖于序列末尾的异常,而不是每次迭代中的异常。