try/except в Python — обработка ошибок для аналитика

Коротко

try/except — механизм обработки ошибок (исключений) в Python. Вместо того чтобы программа падала при ошибке, вы перехватываете её и решаете, что делать дальше. Аналитик сталкивается с этим постоянно: файл не найден, деление на ноль в метрике, битая строка в CSV. На собеседованиях спрашивают про разницу except Exception и голого except, про finally и подход EAFP.

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

try:
    result = 10 / 0
except ZeroDivisionError:
    result = None
    print('Деление на ноль')

Код внутри try выполняется. Если возникает исключение указанного типа — управление переходит в except. Если ошибки нет — except пропускается.

Перехват конкретных исключений

Всегда ловите конкретный тип ошибки, а не все подряд:

config = {'host': 'localhost', 'port': 5432}

try:
    password = config['password']
except KeyError:
    password = 'default'

try:
    value = int('abc')
except ValueError:
    value = 0

Несколько блоков except

def safe_read_metric(filepath):
    try:
        with open(filepath) as f:
            return float(f.read().strip())
    except FileNotFoundError:
        print(f'Файл {filepath} не найден')
        return None
    except ValueError:
        print('Не удалось преобразовать в число')
        return None

Можно объединить типы в один блок: except (ValueError, TypeError):.

Основные типы исключений

Исключение Когда возникает
ValueError Неверное значение: int('abc')
KeyError Ключ отсутствует в словаре
TypeError Неверный тип: '2' + 2
IndexError Индекс за пределами списка
FileNotFoundError Файл не найден при чтении
ZeroDivisionError Деление на ноль
AttributeError Атрибут или метод не существует
ImportError Модуль не удалось импортировать

else и finally

try:
    data = load_report('report.csv')
except FileNotFoundError:
    print('Отчёт не найден')
else:
    # Выполняется, только если ошибки НЕ было
    print(f'Загружено {len(data)} строк')
finally:
    # Выполняется ВСЕГДА — и при ошибке, и без
    print('Завершение обработки')

finally полезен для освобождения ресурсов: закрытие соединения, удаление временного файла. Он выполнится даже если внутри try стоит return.

raise — выбрасывание исключений

def conversion_rate(purchases, visits):
    if visits == 0:
        raise ValueError('visits не может быть 0')
    return purchases / visits

# Перевыброс — ловим, логируем, бросаем дальше
try:
    rate = conversion_rate(10, 0)
except ValueError as e:
    print(f'Ошибка: {e}')
    raise  # пробросить исключение выше

raise без аргументов пробрасывает текущее исключение — это стандартный паттерн для логирования с сохранением traceback.

Примеры для аналитика

Безопасное деление метрик

def safe_divide(a, b, default=0.0):
    try:
        return a / b
    except ZeroDivisionError:
        return default

cr = safe_divide(purchases, visits)
arpu = safe_divide(revenue, users)

Обработка пропущенных ключей

def get_segment_metric(report, segment, metric):
    try:
        return report[segment][metric]
    except KeyError:
        return None

# Вместо цепочки if segment in report and metric in report[segment]
value = get_segment_metric(report, 'organic', 'revenue')

Парсинг дат

from datetime import datetime

def parse_date(s):
    for fmt in ('%Y-%m-%d', '%d.%m.%Y', '%d/%m/%Y'):
        try:
            return datetime.strptime(s, fmt)
        except ValueError:
            continue
    raise ValueError(f'Неизвестный формат даты: {s}')

EAFP vs LBYL

Два подхода к обработке ошибок:

  • LBYL (Look Before You Leap) — проверить перед действием: if key in d: d[key]
  • EAFP (Easier to Ask Forgiveness than Permission) — сделать и поймать ошибку: try: d[key] except KeyError:

Python поощряет EAFP. Это быстрее при частых успехах (нет двойной проверки) и защищает от race conditions. На собеседованиях полезно знать оба термина и уметь объяснить разницу.

Частые ошибки

Голый except: без типа. Ловит вообще всё, включая KeyboardInterrupt и SystemExit. Программу нельзя остановить через Ctrl+C. Минимум используйте except Exception:.

# Плохо
try:
    process_data()
except:
    pass

# Нормально
try:
    process_data()
except Exception as e:
    logging.error(f'Ошибка обработки: {e}')

Глушение ошибок через pass. Ошибка произошла, но вы о ней не узнали — данные молча испорчены. Всегда логируйте или хотя бы возвращайте дефолтное значение осмысленно.

Слишком широкий try-блок. Оборачивайте минимум кода — только ту строку, которая может упасть. Иначе поймаете не ту ошибку и потратите часы на отладку.

Вопросы с собеседований

Чем except Exception отличается от голого except?except Exception ловит все обычные ошибки, но пропускает SystemExit, KeyboardInterrupt и GeneratorExit — они наследуются от BaseException, а не от Exception. Голый except: ловит вообще всё, что мешает корректному завершению программы.

Когда выполняется блок finally? — Всегда: и при ошибке, и без, и даже если внутри try стоит return. Единственное исключение — os._exit() или аварийное завершение процесса.

Что такое EAFP? Приведите пример. — Easier to Ask Forgiveness than Permission — pythonic-подход, при котором сначала выполняют действие, а ошибку ловят через try/except. Противоположность — LBYL (проверка перед действием через if). Пример: try: value = d[key] вместо if key in d: value = d[key].

Зачем нужен else в try/except/else? — Код в else выполняется, только если try завершился без ошибок. Это позволяет отделить «рискованный» код от кода, который должен выполняться только при успехе, и не ловить лишние исключения.

Можно ли создать своё исключение? — Да, нужно унаследоваться от Exception: class InsufficientDataError(Exception): pass. Кастомные исключения полезны в библиотеках и крупных проектах для различения типов ошибок.

Потренировать Python-вопросы на практике можно в тренажёре Карьерника. Подробнее про словари и чтение CSV через pandas. Больше примеров вопросов — на отдельной странице.

Открыть тренажёр в Telegram — вопросы по Python, pandas, SQL и аналитике. Бесплатно.

FAQ

Нужно ли оборачивать весь код в try/except?

Нет. Оборачивайте только те участки, где ошибка ожидаема и вы знаете, как её обработать: чтение файлов, парсинг данных, обращение к API. Необработанное исключение с traceback — это нормально на этапе разработки, оно показывает реальную проблему.

Влияет ли try/except на производительность?

Если ошибки не возникает — почти не влияет. Вход в try-блок практически бесплатный. Замедление происходит только при реальном выбросе исключения. Поэтому EAFP эффективен, когда ошибки — редкость.

Чем try/except отличается от if/else?

if/else — проверка условий. try/except — обработка ошибок, которые возникают во время выполнения. Используйте if для штатной логики ветвления, try/except — для ситуаций, когда что-то может пойти не так (файл пропал, сервер не ответил, формат данных неожиданный).

Как логировать исключения?

Используйте модуль logging с методом exception() — он автоматически добавляет traceback: except Exception as e: logging.exception('Ошибка'). В скриптах для анализа данных достаточно print, но в продакшене всегда используйте logging.