Хорошая ли практика использовать 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 без остановки/выхода из программы
Как проверить, существует ли переменная?
Правильный способ использования try/except с модулем requests в Python?
Зачем в Python нужен блок "finally"?
Есть ли разница между поднятием экземпляра класса Exception и самого класса Exception?