Исключения для 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. Это чаще всего используется в ситуациях, подобных описанию, когда вы добираетесь до конца какой-то последовательности.
По-моему, это совершенно правильное использование исключения. Однако вы хотите быть осторожным при использовании обработки исключений волей-неволей. Получение исключения - достаточно дорогостоящая операция, поэтому лучше всего полагаться только на исключение в конце последовательности, а не на каждой итерации.