Общее мнение «не используйте исключения!» В основном исходит от других языков, и даже там иногда устарели.
В 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, которые будут чистыми, интуитивно понятными, простыми в использовании и трудными для неправильного использования.
collections.defaultdict
илиmy_dict.get(key, default)
делает код намного понятнееtry: my_dict[key] except: return default
НЕТ! - не в целом - исключения не считаются хорошей практикой управления потоками, за исключением одного класса кода. Единственное место, где исключения считаются разумным или даже лучшим способом сигнализации о состоянии, - это операции генератора или итератора. Эти операции могут возвращать любое возможное значение в качестве действительного результата, поэтому необходим механизм для оповещения о завершении.
Подумайте о том, чтобы прочитать двоичный файл потока по одному байту за раз - абсолютно любое значение является потенциально допустимым результатом, но нам все равно нужно сигнализировать об окончании файла. Таким образом, у нас есть выбор: возвращать два значения (значение байта и действительный флаг) каждый раз или выдавать исключение, когда больше ничего не нужно делать. В двух случаях код потребления может выглядеть следующим образом:
альтернативно:
Но с тех пор, как PEP 343 был внедрен и перенесен обратно, все это было аккуратно заключено в
with
заявлении. Вышесказанное становится очень питоническим:В python3 это стало:
Я настоятельно призываю вас прочитать PEP 343, в котором приводится справочная информация, обоснование, примеры и т. Д.
Также обычно используется исключение для оповещения об окончании обработки, когда используются функции генератора для оповещения об окончании.
Я хотел бы добавить, что ваш пример поисковика почти наверняка задом наперед, такие функции должны быть генераторами, возвращающими первое совпадение при первом вызове, затем вызовами-заместителями, возвращающими следующее совпадение, и вызывать
NotFound
исключение, когда совпадений больше нет .источник
if
будет лучше. Когда дело доходит до отладки и тестирования или даже рассуждений о поведении вашего кода, лучше не полагаться на исключения - они должны быть исключением. В производственном коде я видел вычисление, которое возвращалось с помощью метода throw / catch, а не простого возврата, это означало, что когда вычисление достигло ошибки деления на ноль, оно возвращало случайное значение, а не сбой, где произошла ошибка, что вызвало несколько часов отладки.