Хорошая ли практика использовать try-except-else в Python?
У меня возник вопрос о конструкции try-except-else
в Python. Иногда я сталкиваюсь с таким кодом:
try:
try_this(whatever)
except SomeException as exception:
# Обработка исключения
else:
return something
В чем причина существования блока try-except-else
?
Я не люблю такой подход в программировании, поскольку использование исключений для управления потоком исполнения кажется мне нецелесообразным. Если такая конструкция присутствует в языке, значит, на то должна быть веская причина, не так ли?
Я понимаю, что исключения — это не ошибки, и их следует использовать только для исключительных ситуаций (например, если я пытаюсь записать файл на диск, но на нем не осталось места, или если у меня нет разрешения), а не для управления потоком.
Обычно я обрабатываю исключения следующим образом:
something = some_default_value
try:
something = try_this(whatever)
except SomeException as exception:
# Обработка исключения
finally:
return something
Или, если я действительно не хочу возвращать ничего в случае возникновения исключения, то использую:
try:
something = try_this(whatever)
return something
except SomeException as exception:
# Обработка исключения
Буду признателен за разъяснения по поводу использования try-except-else
в Python.
5 ответ(ов)
Ваш вопрос касается блока try-except-else-finally
в Python, и вы привели отличный пример для его иллюстрации. Давайте разберёмся, как это работает.
Вот ваш код с комментариями:
for i in range(3): # Проходим по значениям i от 0 до 2
try:
y = 1 / i # Попытка выполнить деление
except ZeroDivisionError: # Обработка исключения деления на ноль
print(f"\ti = {i}")
print("\tError report: ZeroDivisionError")
else: # Этот блок выполняется, если нет исключений
print(f"\ti = {i}")
print(f"\tNo error report and y equals {y}")
finally: # Этот блок всегда выполняется, независимо от наличия исключений
print("Try block is run.")
Результат выполнения кода будет следующим:
i = 0
Error report: ZeroDivisionError
Try block is run.
i = 1
No error report and y equals 1.0
Try block is run.
i = 2
No error report and y equals 0.5
Try block is run.
Объяснение:
Первая итерация (
i = 0
):try
блок вызывает исключениеZeroDivisionError
, так как деление на 0 невозможно.- Управление переходит в
except
, где выводится сообщение об ошибке. finally
блок выполняется, выводя "Try block is run."
Вторая итерация (
i = 1
):- Деление выполняется успешно, и переменная
y
получает значение1.0
. - Управление переходит в
else
, где выводится значениеy
. finally
блок снова выполняется.
- Деление выполняется успешно, и переменная
Третья итерация (
i = 2
):- Деление выполняется,
y
становится равным0.5
. - Во
else
блоке выводится это значение. finally
блок выполняется.
- Деление выполняется,
Таким образом, блоки try
, except
, else
и finally
позволяют гибко обрабатывать ошибки и гарантировать выполнение определённых действий независимо от того, произошла ошибка или нет. Надеюсь, это проясняет вашу ситуацию! Если у вас есть дополнительные вопросы, не стесняйтесь задавать их.
При использовании блока finally
необходимо быть осторожным, так как он работает иначе, чем блок else
в конструкции try...except
. Блок finally
выполняется независимо от результата выполнения блока try...except
.
Вот пример:
dict_ = {"a": 1}
try:
dict_["b"]
except KeyError:
pass
finally:
print("something")
В этом случае, даже если ключа "b"
нет в словаре и возникает исключение KeyError
, блок finally
все равно выполнится и напечатает "something".
С другой стороны, использование блока else
делает ваш код более читаемым, так как он выполняется только в том случае, если исключение не было выброшено:
try:
dict_["b"]
except KeyError:
pass
else:
print("something")
В этом примере блок else
не выполнится, так как при обращении к несуществующему ключу "b"
возникнет исключение KeyError
. Поэтому код внутри блока else
не будет выполнен.
Таким образом, если вам нужно выполнить некоторый код только в случае успешного завершения блока try
, использование блока else
предпочтительнее, чем finally
.
Хотя никто другой пока не высказывал такое мнение, я бы посоветовал
избегать использования
else
в блокахtry/except
, потому что это непонятно большинству людей.
В отличие от ключевых слов try
, except
и finally
, смысл else
не очевиден; это снижает читаемость. Поскольку else
используется довольно редко, это может заставить других разработчиков, читающих ваш код, перепроверять документацию, чтобы убедиться, что они правильно понимают, что происходит.
Я пишу этот ответ именно потому, что наткнулся на try/except/else
в своей кодовой базе, и это вызвало у меня недоумение, заставив меня погуглить.
Поэтому, когда я вижу код, напоминающий пример автора вопроса:
try:
try_this(whatever)
except SomeException as the_exception:
handle(the_exception)
else:
# выполняем дополнительные действия в случае отсутствия исключения
return something
Я предпочел бы переписать его так:
try:
try_this(whatever)
except SomeException as the_exception:
handle(the_exception)
return # <1>
# выполняем дополнительные действия в случае отсутствия исключения <2>
return something
- <1> Явный
return
четко показывает, что в случае исключения мы завершили выполнение. - <2> Как приятный побочный эффект, код, который раньше находился в блоке
else
, теперь сдвинут на один уровень влево.
Когда вы видите такой код:
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
Использование contextlib.suppress
позволяет более элегантно обрабатывать исключения, устраняя необходимость в ручном использовании блока try/except
. Это делает код более чистым, а его намерение более очевидным. Вы просто "гасите" конкретное исключение, и если оно возникает, выполнение продолжится без прерываний, что упрощает обработку ошибок в рамках данной конструкции.
Это мой простой пример, иллюстрирующий, как работает блок try-except-else-finally в Python:
def div(a, b):
try:
a / b
except ZeroDivisionError:
print("Обнаружена ошибка деления на ноль")
else:
print("Нет ошибки деления на ноль")
finally:
print("В конце деление %d/%d завершено" % (a, b))
Давайте попробуем выполнить деление 1/1:
div(1, 1)
Вывод будет следующим:
Нет ошибки деления на ноль
В конце деление 1/1 завершено
Теперь попробуем деление 1/0:
div(1, 0)
Вывод будет следующим:
Обнаружена ошибка деления на ноль
В конце деление 1/0 завершено
Таким образом, блок try
пытается выполнить код внутри себя, и если возникает исключение (например, ZeroDivisionError
), управление передается в блок except
. Если исключения нет, выполняется блок else
. В любом случае, блок finally
будет выполнен в конце, независимо от того, произошло исключение или нет.
Поймать и вывести полный трейсбек исключения в Python без остановки/выхода из программы
Как проверить, существует ли переменная?
Как протестировать, что функция Python вызывает исключение?
Зачем в Python нужен блок "finally"?
Переизбрасывание исключения в Python с сохранением трассировки стека