В Python обработка исключений реализуется через конструкции try/except/else/finally и оператор raise.
try определяет защищаемый блок кода. Если внутри возникает исключение, управление передаётся в ближайший подходящий блок except. Если исключения нет — выполняется блок else, если он есть. Блок finally исполняется всегда — независимо от того, было исключение или нет (например, для освобождения ресурсов).
Оператор raise вызывает исключение явно или повторяет текущее (в блоке except без указания типа).
Пример:
try: x = 1 / 0 except ZeroDivisionError as e: print(f"Ошибка: {e}") else: print("Ошибки не было") finally: print("Всегда исполняется")
Вывод:
Ошибка: division by zero
Всегда исполняется
Тонкости:
Вопрос: «Что будет, если и в блоке try, и в finally вызвать raise разные исключения?»
Ответ: Исключение из finally "перекроет" исключение из try: наружу выйдет то, что вылетело в finally.
def foo(): try: raise ValueError("в try") finally: raise IndexError("в finally") try: foo() except Exception as e: print(repr(e)) # Выведет: IndexError('в finally')
История
В ETL-процессе в блоке finally безусловно закрывалось соединение с базой, но забывали, что при этом в finally может вылетать исключение (например, если соединение уже закрыто). Итог — "скрытое" исключение из finally полностью поглощало исключение из основного кода, резко усложняя отладку.
История
Использовали цепочки нескольких except: от общего except Exception выше частных. В результате все специфические except оставались "мертвыми" — мелкоуровневые исключения не ловились отдельно, что затрудняло обработку специфических ошибок.
История
В web-сервисе в except блока забывали пробрасывать исключение дальше через "raise", логируя ошибку, но позволяя выполнению идти дальше. В итоге реальные ошибки "терялись", а программа продолжала работать с некорректным состоянием.