Блоки try и except: как писать надёжный код

Раздел: Основы Python -> Исключения и обработка ошибок

Основы обработки исключений

Наиболее эффективный способ обработки исключений в Python - использование конструкции try-except. Она позволяет перехватывать ошибки во время выполнения программы и реагировать на них без аварийного завершения.

Базовый синтаксис:

try:
    risky_code()
except SomeException:
    handle_error()

Python создать ошибку (создание исключений в python)

Пример с делением на ноль:

try:
    result = 10 / 0
except ZeroDivisionError:
    print("Деление на ноль запрещено")

класс ошибок python (классы исключений в python)

Деление на ноль запрещено

Python except (обработка исключений в python с помощью try-except)

Как отловить только определённый тип исключения?

Указывается конкретный класс исключения после except.

try:
    value = int("abc")
except ValueError:
    print("Не удалось преобразовать строку в число")
Не удалось преобразовать строку в число

Если исключение другого типа, оно не будет перехвачено и программа завершится с ошибкой.

Проблема: легко ошибиться в написании имени исключения или забыть импортировать класс. Также не рекомендуется перехватывать слишком общие исключения, такие как Exception, без необходимости.

Как обработать разные исключения по-разному?

Используется несколько блоков except:

try:
    x = int(input("Введите число: "))
    result = 100 / x
except ValueError:
    print("Введите корректное число")
except ZeroDivisionError:
    print("Нельзя делить на ноль")
except Exception as e:
    print(f"Неизвестная ошибка: {e}")

Порядок блоков важен: сначала идут более специфичные исключения, затем общие.

Как выполнить код, если ошибка не произошла?

Блок else выполняется, только если try завершился без исключения.

try:
    number = int("42")
except ValueError:
    print("Некорректное число")
else:
    print(f"Число успешно получено: {number}")
Число успешно получено: 42
Проблема: часто разработчики помещают код, который должен выполняться только при успехе, в try после рискованной операции. Это смешивает логику и может привести к нежелательному перехвату исключений, не связанных с проверяемой операцией.

Как гарантировать выполнение кода независимо от ошибки?

Блок finally выполняется всегда, даже если в try или except был return или выброшено новое исключение.

try:
    f = open("file.txt", "r")
    content = f.read()
except FileNotFoundError:
    print("Файл не найден")
finally:
    print("Закрытие файла (если открыт)")
    if 'f' in locals():
        f.close()
Файл не найден
Закрытие файла (если открыт)
Проблема: finally не должен содержать код, который может выбросить новое исключение, так как оно заменит исходное (если оно было) и затруднит отладку.

Как перехватить любое исключение?

Можно использовать except без указания типа или с Exception. Однако это считается плохой практикой, так как скрывает неожиданные ошибки.

try:
    risky_operation()
except:
    print("Что-то пошло не так")

Лучше записывать информацию об исключении:

try:
    risky_operation()
except Exception as e:
    print(f"Ошибка: {e}")
Проблема: при использовании голого except перехватываются даже системные исключения (SystemExit, KeyboardInterrupt), что может привести к невозможности завершить программу. Рекомендуется использовать except Exception.

Как повторно возбудить исключение после обработки?

Используется ключевое слово raise без аргументов внутри блока except.

try:
    value = int("abc")
except ValueError:
    print("Была ошибка преобразования")
    raise  # повторно возбуждает перехваченное исключение

Также можно изменить исключение, создав новое с помощью raise ... from.

Проблема: если не указать raise, исключение будет подавлено. При повторном возбуждении важно, чтобы не произошло бесконечной рекурсии обработчиков.

Расширенные примеры работы с исключениями

Создание собственного исключения

Пример
class ValidationError(Exception):
    """Исключение для ошибок валидации данных."""
    pass

def validate_age(age):
    if age < 0 or age > 150:
        raise ValidationError("Некорректный возраст")

try:
    validate_age(200)
except ValidationError as e:
    print(f"Ошибка валидации: {e}")
Ошибка валидации: Некорректный возраст

Цепочка исключений (raise from)

Пример
try:
    int("abc")
except ValueError as orig_error:
    raise RuntimeError("Не удалось преобразовать строку") from orig_error

Вывод (если не перехватить):

Traceback (most recent call last):
  ...
ValueError: invalid literal for int() with base 10: 'abc'

The above exception was the direct cause of the following exception:

RuntimeError: Не удалось преобразовать строку

Игнорирование исключений с contextlib.suppress

Пример
from contextlib import suppress
import os

with suppress(FileNotFoundError):
    os.remove("temp.txt")  # не возникнет ошибки, если файла нет

Вложенные try-except

Пример
def safe_divide(a, b):
    try:
        try:
            return a / b
        except ZeroDivisionError:
            print("Внутренняя обработка: деление на ноль")
            return None
    except Exception as e:
        print(f"Внешняя обработка: {e}")
        return None

Обработка ошибок при работе с сетью

Пример
import requests

try:
    response = requests.get("https://nonexistent-domain.xyz", timeout=5)
    response.raise_for_status()
except requests.exceptions.ConnectionError:
    print("Не удалось подключиться к серверу")
except requests.exceptions.Timeout:
    print("Превышено время ожидания")
except requests.exceptions.HTTPError as e:
    print(f"HTTP ошибка: {e.response.status_code}")

Логирование traceback

Пример
import logging
import traceback

logging.basicConfig(level=logging.ERROR)

try:
    1 / 0
except ZeroDivisionError:
    logging.error("Произошла ошибка:\n%s", traceback.format_exc())
ERROR:root:Произошла ошибка:
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ZeroDivisionError: division by zero

Обработка исключений в Python с помощью try-except - comments

En
Python except (python)