Исключения для Python: EAFP и что действительно исключительное?

Было сказано в пару мест (здесь и здесь), что акцент 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 пустым несколько раз, но (возможно) это менее читаемо, чем использование явных условий.

Какой консенсус в отношении использования исключений для такого рода не исключительной программной логики?

Ответ 1

исключения следует вызывать только в действительно исключительные случаи

Не в Python: например, цикл each for (если только он не был преждевременно break или return s)) завершается исключением (StopIteration), которое бросается и ломается. Итак, исключение, которое происходит один раз за цикл, вряд ли странно для Python - это там чаще, чем нет!

Принцип, о котором идет речь, может иметь решающее значение для других языков, но это определенно не является основанием для применения этого принципа к Python, где это противоречит языковой этике.

В этом случае мне нравится Jon rewrite (что должно быть еще проще упрощено, удалив ветку else), потому что это делает код более компактным - прагматическая причина, наиболее определенно не "закалка" стиля Python с инопланетным принципом.

Ответ 2

В большинстве языков низкого уровня, таких как С++, исключение броска стоит дорого. Это влияет на много "общей мудрости" об исключениях и не так сильно распространяется на языки, которые работают в виртуальной машине, например Python. Там нет такой большой стоимости в Python для использования исключения вместо условного.

(Это случай, когда "общая мудрость" становится привычным. Люди приходят к нему из опыта в одном типе среды - языки низкого уровня, а затем применяют его к новым доменам, не оценивая, имеет смысл.)

Исключения по-прежнему, в целом, являются исключительными. Это не означает, что они не часто случаются; это означает, что они являются исключением. Это те вещи, которые, как правило, ломаются от обычного потока кода, и которые большую часть времени вы не хотите обрабатывать один за другим - это точка обработчиков исключений. Эта часть такая же в Python, как и на С++, и на всех других языках с исключениями.

Тем не менее, это имеет тенденцию определять, когда выбрасываются исключения. Вы говорите, когда следует исключать исключения. Очень просто, не беспокойтесь об этом: исключения не дорогие, поэтому не переходите к длине, чтобы попытаться предотвратить их выброс. В этом плане много кода Python.

Я не согласен с предложением Джона попробовать протестировать и заранее избежать исключений. Это прекрасно, если это приводит к более четкому коду, как в его примере. Однако во многих случаях это просто осложняет ситуацию - это может привести к дублированию проверок и внедрению ошибок. Например,

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")

Конечно, мы можем проверить и избежать неудачи - но мы усложнили ситуацию. Мы пытаемся угадать все способы доступа к файлам (и это не улавливает их всех), возможно, мы ввели условия гонки, и мы делаем намного больше операций ввода-вывода. Это все сделано для нас - просто поймайте исключение.

Ответ 3

Глядя на документы Я думаю, что вы можете безопасно переписать функцию следующим образом:

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.

В конце списка, который, как вы знаете, произойдет здесь, не является исключительным - он garaunteed! Поэтому лучше было бы справиться с этим заранее. Если у вас было что-то еще в другом потоке, который потреблялся из одной кучи, то с помощью try-except, но там было бы намного больше смысла (т.е. Обработки специального/непредсказуемого случая).

В более общем плане, я бы избегал исключений для тестирования, где бы я ни тестировался, и избегать неудачных попыток. Это заставляет вас сказать: "Я знаю, что эта плохая ситуация может произойти, так вот как я с этим справляюсь". По моему мнению, в результате вы будете писать более читаемый код.

[Изменить] Обновлен пример в соответствии с предложением Alex

Ответ 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).

Ответ 5

Я нашел практику использования исключений, поскольку "обычные" средства управления потоком были достаточно широко распространены в Python. Это чаще всего используется в ситуациях, подобных описанию, когда вы добираетесь до конца какой-то последовательности.

По-моему, это совершенно правильное использование исключения. Однако вы хотите быть осторожным при использовании обработки исключений волей-неволей. Получение исключения - достаточно дорогостоящая операция, поэтому лучше всего полагаться только на исключение в конце последовательности, а не на каждой итерации.