Общее мнение «не используйте исключения!» В основном исходит от других языков, и даже там иногда устарели.
В C ++, метание исключения является очень дорогостоящими из - за «стек разматывание». Каждое объявление локальной переменной похоже на with
оператор в Python, и объект в этой переменной может запускать деструкторы. Эти деструкторы выполняются как при возникновении исключения, так и при возврате из функции. Эта «RAII-идиома» является неотъемлемой особенностью языка и очень важна для написания надежного и корректного кода - поэтому RAII по сравнению с дешевыми исключениями был компромиссом, который C ++ решил сделать для RAII.
В ранних версиях C ++ большая часть кода не была написана безопасным для исключений способом: если вы на самом деле не используете RAII, легко утечь память и другие ресурсы. Таким образом, создание исключений сделает этот код неправильным. Это больше не разумно, поскольку даже стандартная библиотека C ++ использует исключения: вы не можете притворяться, что исключения не существуют. Тем не менее, исключения по-прежнему являются проблемой при объединении кода C с C ++.
В Java каждое исключение имеет связанную трассировку стека. Трассировка стека очень полезна при отладке ошибок, но затрачивается впустую, когда исключение никогда не печатается, например, потому что оно использовалось только для потока управления.
Поэтому в этих языках исключения являются «слишком дорогими» для использования в качестве потока управления. В Python это не проблема, а исключения намного дешевле. Кроме того, язык Python уже страдает от некоторых накладных расходов, которые делают стоимость исключений незаметной по сравнению с другими конструкциями потока управления: например, проверка наличия записи dict с помощью явного теста членства if key in the_dict: ...
обычно выполняется так же быстро, как простой доступ к записи the_dict[key]; ...
и проверка, если вы получить ключевую ошибку. Некоторые интегральные языковые функции (например, генераторы) разработаны с точки зрения исключений.
Таким образом, хотя нет никаких технических причин специально избегать исключений в Python, все еще остается вопрос, следует ли вам использовать их вместо возвращаемых значений. Проблемы уровня дизайна с исключениями:
они совсем не очевидны. Вы не можете легко взглянуть на функцию и увидеть, какие исключения она может генерировать, поэтому вы не всегда знаете, что перехватить. Возвращаемое значение имеет тенденцию быть более четким.
Исключением является нелокальный поток управления, который усложняет ваш код. Когда вы генерируете исключение, вы не знаете, где возобновится поток управления. Для ошибок, которые не могут быть немедленно обработаны, это, вероятно, хорошая идея, при уведомлении вашего вызывающего абонента о состоянии это совершенно не нужно.
Культура Python, как правило, склоняется в пользу исключений, но ее легко обойти. Представьте себе list_contains(the_list, item)
функцию, которая проверяет, содержит ли список элемент, равный этому элементу. Если результат передается через исключения, это очень раздражает, потому что мы должны назвать его так:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
Возвращение bool было бы намного понятнее:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
Если функция уже должна возвращать значение, то возвращение специального значения для особых условий довольно подвержено ошибкам, потому что люди забудут проверить это значение (это, вероятно, является причиной 1/3 проблем в C). Исключение обычно более правильное.
Хорошим примером является pos = find_string(haystack, needle)
функция, которая ищет первое вхождение needle
строки в строке haystack и возвращает начальную позицию. Но что, если в их стоге сена нет струны иглы?
Решение C и имитация Python - вернуть специальное значение. В C это нулевой указатель, в Python это -1
. Это приведет к неожиданным результатам, когда позиция используется в качестве строкового индекса без проверки, особенно если -1
это допустимый индекс в Python. В С ваш указатель NULL, по крайней мере, даст вам ошибку.
В PHP возвращается специальное значение другого типа: логическое значение FALSE
вместо целого числа. Как оказалось, на самом деле это не лучше из-за неявных правил преобразования языка (но учтите, что и в Python логические значения могут использоваться как целые числа!). Функции, которые не возвращают согласованный тип, обычно считаются очень запутанными.
Более надежным вариантом было бы генерировать исключение, когда строка не может быть найдена, что гарантирует, что во время обычного потока управления невозможно случайно использовать специальное значение вместо обычного значения:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
В качестве альтернативы всегда можно использовать возвращаемый тип, который не может быть использован напрямую, но должен быть сначала развернут, например, кортеж result-bool, где логическое значение указывает, произошло ли исключение или пригоден ли результат. Потом:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
Это заставляет вас немедленно решать проблемы, но это очень быстро раздражает. Это также препятствует тому, чтобы вы легко включили функцию цепочки. Каждый вызов функции теперь требует трех строк кода. Голанг - это язык, который считает, что это неудобство стоит безопасности.
Подводя итог, можно сказать, что исключения не совсем без проблем и могут быть окончательно использованы, особенно когда они заменяют «нормальное» возвращаемое значение. Но когда они используются для сигнализации особых условий (не обязательно просто ошибок), исключения могут помочь вам в разработке API, которые будут чистыми, интуитивно понятными, простыми в использовании и трудными для неправильного использования.