Хорошая практика использования try-except-else в Python?
224 Juan Antonio Gomez Moriano [2013-04-22 04:44:00]
Время от времени в Python я вижу блок:
try:
try_this(whatever)
except SomeException as exception:
#Handle exception
else:
return something
В чем причина существования try-except-else?
Мне не нравится такое программирование, поскольку он использует исключения для управления потоком. Однако, если он включен в язык, для этого должна быть веская причина, не так ли?
Я понимаю, что исключения не являются ошибками, и что их следует использовать только для исключительных условий (например, я пытаюсь записать файл на диск, и нет больше места, или, может быть, я не имеют разрешения), а не для управления потоком.
Обычно я обрабатываю исключения как:
something = some_default_value
try:
something = try_this(whatever)
except SomeException as exception:
#Handle exception
finally:
return something
Или, если я действительно ничего не хочу возвращать, если произойдет исключение, тогда:
try:
something = try_this(whatever)
return something
except SomeException as exception:
#Handle exception
python exception exception-handling try-catch
8 ответов
343 Решение Raymond Hettinger [2013-04-22 06:13:00]
"Я не знаю, если это из-за невежества, но мне это не нравится вроде программирования, поскольку он использует исключения для управления потоком".
В мире Python использование исключений для управления потоком является обычным и нормальным.
Даже разработчики ядра Python используют исключения для управления потоком, и этот стиль сильно запекается в языке (т.е. в протоколе итератора используется StopIteration к завершению цикла сигнала).
Кроме того, стиль try-except-style используется для предотвращения условий гонки, присущих некоторым "look-before-you-leap" . Например, тестирование os.path.exists приводит к информации, которая может быть устаревшей к моменту ее использования. Аналогично, Queue.full возвращает информацию, которая может быть устаревшей. стиль try-except-else даст более надежный код в этих случаях.
"Я понимаю, что исключения - это не ошибки, они должны использоваться для исключительных условий"
В некоторых других языках это правило отражает их культурные нормы, отраженные в их библиотеках. "Правило" также частично основано на соображениях производительности для этих языков.
Культурная норма Питона несколько отличается. Во многих случаях вы должны использовать исключения для потока управления. Кроме того, использование исключений в Python не замедляет окружающий код и код вызова, как это происходит на некоторых скомпилированных языках (т.е. CPython уже реализует код для проверки исключений на каждом шаге, независимо от того, используете ли вы фактически исключения или нет).
Другими словами, ваше понимание того, что "исключения для исключительного" - это правило, которое имеет смысл в некоторых других языках, но не для Python.
"Однако, если он включен в сам язык, должно быть хорошая причина для этого, не так ли?"
Помимо помощи во избежание условий гонки, исключения также очень полезны для выведения ошибок при обращении с внешними циклами. Это необходимая оптимизация в интерпретируемых языках, которые не имеют автоматического циклического движения курсора цикла.
Кроме того, исключения могут упростить код довольно часто в обычных ситуациях, когда способность справляться с проблемой далека от того, откуда возникла проблема. Например, обычно существует код вызова кода пользовательского интерфейса верхнего уровня для бизнес-логики, который, в свою очередь, вызывает низкоуровневые процедуры. Ситуации, возникающие в низкоуровневых процедурах (например, дублирующие записи для уникальных ключей в доступе к базам данных), могут обрабатываться только в коде верхнего уровня (например, запрашивать у пользователя новый ключ, который не конфликтует с существующими ключами). Использование исключений для такого рода потоков управления позволяет программам среднего уровня полностью игнорировать проблему и прекрасно отделяться от этого аспекта управления потоком.
Существует хорошая запись в блоге о незаменимости исключений здесь.
Также см. этот ответ StackOverFlow: Действительно ли исключения для исключительных ошибок?
"В чем причина существования try-except-else?"
Самое интересное - это предложение else. Он запускается, когда нет исключения, но перед окончательным предложением. Это его основная цель.
Без предложения else единственная опция запуска дополнительного кода перед финализацией будет неуклюжей практикой добавления кода в try-clause. Это неудобно, потому что оно рискует исключая исключения из кода, который не был предназначен для защиты с помощью блока try.
Случай использования дополнительного незащищенного кода до финализации не возникает очень часто. Поэтому не ожидайте увидеть много примеров в опубликованном коде. Это несколько редка.
Другим вариантом использования else-clause является выполнение действий, которые должны произойти, когда не возникает исключение и которые не возникают при обработке исключений. Например:
recip = float('Inf')
try:
recip = 1 / f(x)
except ZeroDivisionError:
logging.info('Infinite result')
else:
logging.info('Finite result')
Наконец, наиболее частое использование else-предложения в try-блоке - это немного благоухание (выравнивание исключительных результатов и исключающих результатов на одном уровне отступов). Это использование всегда является необязательным и не является строго необходимым.
73 Aaron Hall [2015-07-25 16:30:00]
БлокВ чем причина существования try-except-else?
A try позволяет обрабатывать ожидаемую ошибку. Блок except должен обрабатывать только исключения, которые вы готовы обработать. Если вы справляетесь с непредвиденной ошибкой, ваш код может ошибиться и скрыть ошибки.
Предложение else будет выполняться, если ошибок не было, и, не выполняя этот код в блоке try, вы избегаете обнаружения неожиданной ошибки. Опять же, обнаружение неожиданной ошибки может скрыть ошибки.
Пример
Например:
try:
try_this(whatever)
except SomeException as the_exception:
handle(the_exception)
else:
return something
В пакете "try, except" есть два необязательных предложения: else и finally. Так что это на самом деле try-except-else-finally.
'else' будет оцениваться только в случае отсутствия исключения из блока try. Это позволяет нам упростить более сложный код ниже:
no_error = None
try:
try_this(whatever)
no_error = True
except SomeException as the_exception:
handle(the_exception)
if no_error:
return something
поэтому, если мы сравним else с альтернативой (которая может создавать ошибки), мы видим, что она сохраняет строки кода и имеет более читаемую, поддерживаемую и менее баггированную базу кода.
finally
finally будет выполняться независимо от того, что даже если другая строка оценивается с помощью оператора return.
Сбой с псевдокодом
Это может помочь сломать это в наименьшей возможной форме, демонстрирующей все функции, с комментариями. Предположим, что это синтаксически корректно (но не выполняется, если имена не определены). Псевдокод находится в функции.
Например:
try:
try_this(whatever)
except SomeException as the_exception:
handle_SomeException(the_exception)
# Handle a instance of SomeException or a subclass of it.
except Exception as the_exception:
generic_handle(the_exception)
# Handle any other exception that inherits from Exception
# - doesn't include GeneratorExit, KeyboardInterrupt, SystemExit
# Avoid bare `except:`
else: # there was no exception whatsoever
return something()
# if no exception, the "something()" gets evaluated,
# but the return will not be executed due to the return in the
# finally block below.
finally:
# this block will execute no matter what, even if no exception,
# after "something" is eval'd but before that value is returned
# but even if there is an exception.
# a return here will hijack the return functionality. e.g.:
return True # hijacks the return in the else clause above
Верно, что мы могли бы включить код в блок else в блок try вместо этого, где он будет работать, если не было исключений, но что, если этот код сам вызывает исключение того типа, re ловли? Оставить его в блоке try скрыть эту ошибку.
Мы хотим минимизировать строки кода в блоке try, чтобы избежать улавливания исключений, которых мы не ожидали, по принципу, что если наш код выходит из строя, мы хотим, чтобы он громко рухнул. Это лучшая практика.
Я понимаю, что исключения не являются ошибками
В Python большинство исключений - ошибки.
Мы можем просмотреть иерархию исключений с помощью pydoc. Например, в Python 2:
$ python -m pydoc exceptions
или Python 3:
$ python -m pydoc builtins
Дайте нам иерархию. Мы видим, что большинство типов Exception являются ошибками, хотя Python использует некоторые из них для таких вещей, как end for loop (StopIteration). Это иерархия Python 3:
BaseException
Exception
ArithmeticError
FloatingPointError
OverflowError
ZeroDivisionError
AssertionError
AttributeError
BufferError
EOFError
ImportError
ModuleNotFoundError
LookupError
IndexError
KeyError
MemoryError
NameError
UnboundLocalError
OSError
BlockingIOError
ChildProcessError
ConnectionError
BrokenPipeError
ConnectionAbortedError
ConnectionRefusedError
ConnectionResetError
FileExistsError
FileNotFoundError
InterruptedError
IsADirectoryError
NotADirectoryError
PermissionError
ProcessLookupError
TimeoutError
ReferenceError
RuntimeError
NotImplementedError
RecursionError
StopAsyncIteration
StopIteration
SyntaxError
IndentationError
TabError
SystemError
TypeError
ValueError
UnicodeError
UnicodeDecodeError
UnicodeEncodeError
UnicodeTranslateError
Warning
BytesWarning
DeprecationWarning
FutureWarning
ImportWarning
PendingDeprecationWarning
ResourceWarning
RuntimeWarning
SyntaxWarning
UnicodeWarning
UserWarning
GeneratorExit
KeyboardInterrupt
SystemExit
23 Gareth Latty [2013-04-22 04:47:00]
Python не соглашается с идеей, что исключения должны использоваться только для исключительных случаев, на самом деле идиома 'просить прощения, а не разрешения'. Это означает, что использование исключений в качестве обычной части вашего управления потоком вполне приемлемо и, по сути, рекомендуется.
Это, как правило, хорошо, так как работа над этим способом помогает избежать некоторых проблем (как очевидный пример, часто избегают условий гонки), и это делает код более понятным.
Представьте, что у вас есть ситуация, когда вы берете какой-то пользовательский ввод, который нужно обработать, но имеете по умолчанию, который уже обработан. Структура try: ... except: ... else: ... обеспечивает очень читаемый код:
try:
raw_value = int(input())
except ValueError:
value = some_processed_value
else: # no error occured
value = process_value(raw_value)
Сравните с тем, как он может работать на других языках:
raw_value = input()
if valid_number(raw_value):
value = process_value(int(raw_value))
else:
value = some_processed_value
Обратите внимание на преимущества. Нет необходимости проверять, что значение действительно, и разобрать его отдельно, они выполняются один раз. Код также следует за более логичной прогрессией, сначала начинается основной путь кода, а затем "если он не работает, сделайте это".
Пример, естественно, немного надуман, но он показывает, что для этой структуры есть случаи.
13 Aaron Hall [2015-02-03 19:54:00]
Является ли хорошей практикой использование try-except-else в python?
Ответ на этот вопрос заключается в том, что он зависит от контекста. Если вы это сделаете:
d = dict()
try:
item = d['item']
except KeyError:
item = 'default'
Это демонстрирует, что вы не очень хорошо знаете Python. Эта функциональность инкапсулирована в метод dict.get:
item = d.get('item', 'default')
Блок try/except - гораздо более визуально загроможденный и многословный способ записи того, что может эффективно выполняться в одной строке с помощью атомного метода. Существуют и другие случаи, когда это верно.
Однако это не означает, что мы должны избегать обработки исключений. В некоторых случаях предпочтительно избегать условий гонки. Не проверяйте, существует ли файл, просто попытайтесь его открыть и поймайте соответствующий IOError. Ради простоты и удобочитаемости попробуйте инкапсулировать это или скомпенсировать его как можно скорее.
Прочитайте Zen of Python, понимая, что есть принципы, которые находятся в напряжении, и будьте осторожны с догмой, которая слишком сильно зависит от любого из заявлений в ней.
4 Greg [2013-04-22 05:31:00]
Вы должны быть осторожны с использованием блока finally, поскольку это не то же самое, что использовать блок else в try, кроме. Блок finally будет запускаться независимо от результата попытки, кроме.
In [10]: dict_ = {"a": 1}
In [11]: try:
....: dict_["b"]
....: except KeyError:
....: pass
....: finally:
....: print "something"
....:
something
Как все отметили, используя блок else, ваш код становится более читаемым и работает только тогда, когда исключение не выбрано
In [14]: try:
dict_["b"]
except KeyError:
pass
else:
print "something"
....:
3 Rajiv Bakulesh Shah [2017-01-14 02:40:00]
Всякий раз, когда вы видите это:
try:
y = 1 / x
except ZeroDivisionError:
pass
else:
return y
Или даже это:
try:
return 1 / x
except ZeroDivisionError:
return None
Рассмотрим это вместо:
import contextlib
with contextlib.suppress(ZeroDivisionError):
return 1 / x
0 zakiakhmad [2017-11-24 06:22:00]
Это мой простой фрагмент о том, как понять блок try-except-else-finally в Python:
def div(a, b):
try:
a/b
except ZeroDivisionError:
print("Zero Division Error detected")
else:
print("No Zero Division Error")
finally:
print("Finally the division of %d/%d is done" % (a, b))
Попробуйте div 1/1:
div(1, 1)
No error
Finally the division of 1/1 is done
Попробуйте div 1/0
div(1, 0)
Zero Division Error detected
Finally the division of 1/0 is done
-3 Kevin J. Rice [2015-03-20 19:03:00]
ОП, ВЫ ПРАВИЛЬНО. Остальное после try/except в Python является уродливым. это приводит к другому объекту управления потоком, где нет необходимости:
try:
x = blah()
except:
print "failed at blah()"
else:
print "just succeeded with blah"
Полностью явный эквивалент:
try:
x = blah()
print "just succeeded with blah"
except:
print "failed at blah()"
Это намного яснее, чем предложение else. Остальное после try/except не часто записывается, поэтому требуется некоторое время, чтобы понять, что подразумевается.
Просто потому, что вы МОЖЕТЕ сделать что-то, не означает, что вы ДОЛЖНЫ что-то сделать.
На языки добавлено множество функций, потому что кто-то подумал, что это может пригодиться. Проблема в том, что чем больше функций, тем менее ясными и очевидными являются вещи, потому что люди обычно не используют эти колокола и свистки.
Просто мои 5 центов здесь. Я должен зайти позади и очистить много кода, написанного 1-м годом от разработчиков колледжа, которые считают, что они умны и хотят писать код в какой-то uber-tight, uber-efficient, когда это просто заставляет чтобы попытаться прочитать/изменить позже. Я голосую за удобочитаемость каждый день и дважды по воскресеньям.