Уроки курса
1 Введение в Python и философия дзен
30 мин
2 Переменные и динамическая типизация
30 мин
3 Базовые типы данных: числа, строки и булевы значения
30 мин
4 Изменяемые и неизменяемые объекты (Mutable vs Immutable)
30 мин
5 Форматирование строк и f-строки
30 мин
6 Углубленная работа со списками
30 мин
7 Кортежи и их особенности
30 мин
8 Словари под капотом
30 мин
9 Множества и математические операции
30 мин
10 Генераторы списков (List Comprehensions)
30 мин
11 Генераторы словарей и множеств
30 мин
12 Встроенные функции для коллекций
30 мин
13 Условные операторы и логические выражения
30 мин
14 Циклы while и управление потоком
30 мин
15 Итерация с циклом for
30 мин
16 Конструкции for...else и while...else
30 мин
17 Функции enumerate и zip
30 мин
18 Создание собственных функций (def)
30 мин
19 Позиционные и именованные аргументы
30 мин
20 Проблема изменяемых аргументов по умолчанию
30 мин
21 Произвольное число аргументов (*args и **kwargs)
30 мин
22 Область видимости переменных (LEGB)
30 мин
23 Анонимные функции (lambda)
30 мин
24 Функции высшего порядка
30 мин
25 Замыкания (Closures)
30 мин
26 Введение в объектно-ориентированное программирование
30 мин
27 Атрибуты классов и экземпляров
30 мин
28 Магический метод __init__
30 мин
29 Методы экземпляра
30 мин
30 Инкапсуляция и сокрытие данных
30 мин
31 Декоратор @property
30 мин
32 Наследование классов
30 мин
33 Переопределение методов и функция super()
30 мин
34 Полиморфизм в Python
30 мин
35 Магические методы строк (__str__ и __repr__)
30 мин
36 Обработка исключений (try-except)
30 мин
37 Блоки else и finally
30 мин
38 Генерация собственных исключений (raise)
30 мин
39 Открытие и чтение файлов
30 мин
40 Запись данных в файлы
30 мин
41 Контекстные менеджеры (with)
30 мин
42 Работа с форматом JSON
30 мин
43 Модули и импорты
30 мин
44 Полезные модули стандартной библиотеки
30 мин
45 Модуль datetime
30 мин
46 Модуль collections
30 мин
47 Виртуальные окружения (venv)
30 мин
48 Установка сторонних пакетов через pip
30 мин
49 Организация структуры Python-проекта
30 мин
50 Финальный проект: создание приложения
30 мин

Блоки else и finally

Написание кода, который выполняется только при отсутствии ошибок, и кода, который выполняется гарантированно всегда.

Прогресс урока: 0%

Добро пожаловать в продвинутую обработку исключений!

На данном этапе вашего пути к уверенному владению Python (Intermediate Level) вы уже прекрасно понимаете, что программы не всегда работают в идеальных условиях. В реальном мире сети падают, файлы удаляются, базы данных блокируются, а пользователи вводят некорректные данные. Вы уже знакомы с базовой конструкцией try-except, которая позволяет перехватывать ошибки и предотвращать аварийное завершение программы (краш). Однако, профессиональная разработка программного обеспечения требует гораздо более тонкого контроля над потоком выполнения. Базовый перехват ошибок — это лишь вершина айсберга. Настоящее инженерное мастерство заключается в том, чтобы точно разделять логику, которая может вызвать ошибку, логику, которая должна выполниться только при успешном завершении опасной операции, и логику, которая обязана отработать при любом исходе для очистки ресурсов.

В этом уроке мы совершим глубокое погружение в полные возможности механизма обработки исключений в Python, добавив в наш арсенал блоки else и finally. Использование этих блоков не просто делает ваш код более надежным, оно делает его 'Pythonic' — идиоматичным, читаемым и профессиональным. Мы будем разбирать, почему помещение слишком большого количества кода в блок try является известным антипаттерном, который может скрыть реальные баги в приложении. Мы изучим, как блок else решает эту проблему, обеспечивая безопасную зону для кода, зависящего от успешного выполнения try. Затем мы перейдем к блоку finally, который является абсолютным гарантом выполнения, краеугольным камнем управления ресурсами (такими как закрытие файлов, освобождение сетевых сокетов или откаты транзакций баз данных). Мы также разберем сложнейшие вопросы для собеседований уровня Middle, связанные с поведением finally при наличии операторов return, break или continue внутри try. Приготовьтесь, этот урок радикально изменит ваше представление о том, как нужно писать безопасный код!

Проблема 'Толстого' блока Try (Антипаттерн Catch-All)

Прежде чем мы введем новые синтаксические конструкции, давайте разберем проблему, которую они призваны решить. Представьте, что вам нужно открыть конфигурационный файл, прочитать из него данные, преобразовать их в формат JSON, а затем отправить по сети на удаленный сервер. Очень часто начинающие разработчики (и даже некоторые Middle-специалисты в спешке) помещают весь этот многоэтапный процесс в один гигантский блок try. Они рассуждают так: 'Если на любом из этих этапов произойдет сбой, я просто перехвачу ошибку в except Exception и залогирую ее'. Такой подход крайне опасен. Почему? Потому что блок except в этом случае перехватит абсолютно любую ошибку, произошедшую в любом месте этого гигантского куска кода.

Если вы опечатались в имени переменной при формировании JSON (что вызовет NameError), или если библиотека для отправки по сети обновилась и изменила свой API (вызвав TypeError или AttributeError), ваш общий блок except молча проглотит эти ошибки, выведя сообщение вроде 'Не удалось обработать файл'. Вы будете часами искать проблему с файлом, хотя файл читается идеально, а ошибка кроется совершенно в другом месте! Философия Python (Дзен Python) гласит: 'Ошибки никогда не должны проходить молчаливо'. И 'Явное лучше, чем неявное'. Поэтому золотое правило обработки исключений звучит так: блок try должен содержать минимально возможное количество кода — только ту единственную строку или операцию, которая потенциально может выбросить ожидаемое исключение. Все остальное должно находиться за его пределами. И именно здесь на сцену выходит блок else.

python
# АНТИПАТТЕРН: Слишком много кода в блоке try

def process_user_data(filepath):
    try:
        # 1. Открытие файла (может вызвать FileNotFoundError)
        with open(filepath, 'r') as file:
            data = file.read()
        
        # 2. Парсинг данных (может вызвать ValueError или KeyError)
        parsed_data = parse_custom_format(data)
        
        # 3. Вычисление бизнес-логики (может вызвать TypeError, ZeroDivisionError)
        result = calculate_metrics(parsed_data)
        
        # 4. Сохранение результата (может вызвать PermissionError)
        save_to_database(result)
        
    except Exception as e:
        print(f"Произошла ошибка при обработке: {e}")
        # Какая именно операция упала? Мы не знаем! Это ужасный дизайн.

Почему помещение большого количества строк кода в один блок `try` считается плохой практикой (антипаттерном)?

Блок else: Путь Успеха (Happy Path)

Итак, мы установили, что блок try должен быть максимально коротким. Но как быть с кодом, который должен выполниться только в том случае, если операция в блоке try прошла успешно? Если мы поместим этот код после всего блока try-except, он будет выполняться всегда, даже если в try произошла ошибка (предполагая, что except ее перехватил и программа не завершилась). Это часто ломает логику программы: мы не должны парсить файл, если мы не смогли его прочитать! Именно для этой цели в Python существует блок else в контексте обработки исключений. Синтаксически он располагается строго после всех блоков except.

Как работает блок else? Инструкции внутри блока else выполняются только в том случае, если в блоке try не возникло никаких исключений. Если блок try выбрасывает ошибку, управление переходит к соответствующему блоку except, а блок else полностью игнорируется. Это элегантное и мощное решение. Теперь мы можем оставить в блоке try только потенциально опасную операцию (например, преобразование строки в число int() или сетевой запрос), а всю последующую обработку полученных данных, которая сама по себе может содержать баги, вынести в блок else. Если эта последующая обработка в else упадет с ошибкой (например, TypeError или NameError), она не будет случайно перехвачена блоками except, относящимися к блоку try! Программа честно упадет, показав вам точное место бага в бизнес-логике, что критически важно для быстрого исправления дефектов.

python
# ПРАВИЛЬНЫЙ ПОДХОД: Использование блока else

def divide_numbers():
    user_input_a = input("Введите делимое: ")
    user_input_b = input("Введите делитель: ")

    # 1. В try помещаем ТОЛЬКО опасную операцию преобразования и деления
    try:
        num_a = float(user_input_a)
        num_b = float(user_input_b)
        result = num_a / num_b
    except ValueError:
        print("Ошибка: Введено не число!")
    except ZeroDivisionError:
        print("Ошибка: Деление на ноль невозможно!")
    else:
        # 2. Этот блок выполнится ТОЛЬКО если try отработал без ошибок
        # Ошибки, возникшие здесь, НЕ БУДУТ перехвачены верхними except
        print(f"Успех! Результат деления: {result}")
        save_result_to_db(result) # Если тут опечатка или баг, программа упадет честно

В каком случае код внутри блока `else` (в связке с `try-except`) будет выполнен?

Глубокий разбор логики работы else

Давайте разберем поведение интерпретатора Python по шагам, чтобы метод 'Active Recall' сработал на 100%. Представим два сценария выполнения функции из предыдущего примера. Сценарий 1: Успех (Happy Path). Пользователь вводит '10' и '2'. Интерпретатор заходит в блок try. Строка num_a = float('10') выполняется успешно. Строка num_b = float('2') выполняется успешно. Строка result = 10.0 / 2.0 выполняется успешно, result становится 5.0. Поскольку исключений не было, интерпретатор перепрыгивает все блоки except. Достигнув блока else, он видит, что предварительное условие выполнено (ошибок не было), и заходит внутрь. Выполняется print и функция save_result_to_db. Программа продолжает работу дальше по коду.

Сценарий 2: Возникновение ошибки. Пользователь вводит '10' и '0'. Интерпретатор заходит в блок try. num_a становится 10.0, num_b становится 0.0. При попытке вычислить result = 10.0 / 0.0 интерпретатор мгновенно прерывает выполнение блока try (остаток кода в нем никогда не будет выполнен) и генерирует объект исключения ZeroDivisionError. Он начинает проверять блоки except сверху вниз. Первый except ValueError не подходит. Второй except ZeroDivisionError совпадает! Управление передается внутрь этого except, выводится 'Ошибка: Деление на ноль невозможно!'. После того как код внутри except отработал, интерпретатор видит блок else. Но так как в try была ошибка, блок else полностью игнорируется. Функция завершает эту конструкцию и продолжает выполнение дальше (или возвращает управление). Это гарантирует, что мы не попытаемся сохранить в базу данных переменную result, которая даже не была создана из-за сбоя в делении.

Напишите ключевое слово, которое используется для обозначения блока кода, выполняемого только при отсутствии исключений в блоке try:

Блок finally: Абсолютная гарантия выполнения

Мы научились перехватывать ошибки и научились писать код, который выполняется только при успехе. Но что если нам нужно выполнить какое-то действие абсолютно в любом случае? Будь то успешное выполнение, перехваченная ошибка или даже непредвиденная ошибка, которая приведет к аварийному завершению программы? Здесь на арену выходит конструкция finally. Блок finally располагается в самом конце цепочки try-except-else (блоки except и else при этом не являются обязательными, минимальная конструкция может быть просто try-finally).

Для чего он нужен? Блок finally — это менеджер ресурсов и уборщик вашего приложения. Его главная и единственная задача — обеспечить очистку (teardown) и освобождение внешних ресурсов. Вспомните, что программа часто взаимодействует с внешним миром: открывает файлы на жестком диске, устанавливает TCP/IP соединения с удаленными серверами, открывает сессии с базами данных, захватывает системные мьютексы и блокировки. Эти ресурсы конечны. Если ваша программа 'упадет' из-за ошибки в процессе работы и не закроет за собой файл или соединение с БД, этот ресурс останется 'подвешенным' в операционной системе. Со временем таких 'утечек ресурсов' (resource leaks) станет так много, что сервер просто перестанет отвечать на новые запросы, выдавая ошибки вида 'Too many open files' (слишком много открытых файлов) или исчерпав пул соединений базы данных. Блок finally гарантирует, что код внутри него будет выполнен интерпретатором CPython в 99.9% случаев, независимо от того, насколько страшно упала программа внутри блока try.

python
# Классический пример использования finally для очистки ресурсов

def write_log(message):
    file = open("application.log", "a") # Открываем файл (ресурс захвачен)
    try:
        # Пытаемся записать данные. Здесь может произойти всё что угодно:
        # Диск может переполниться, или мы попытаемся записать объект вместо строки
        file.write(message + "\n")
        print("Лог успешно записан.")
    except TypeError:
        print("Ошибка типа: сообщение должно быть строкой.")
    finally:
        # Этот блок выполнится ВСЕГДА!
        # Была ошибка, не было ошибки - файл будет закрыт.
        file.close()
        print("Файл application.log надежно закрыт.")

Каково главное предназначение блока `finally` в Python?

Глубокая анатомия finally: Что значит 'Всегда'?

Когда мы говорим, что блок finally выполняется 'всегда', начинающие разработчики часто воспринимают это скептически. 'А что если я вызову внутри блока try оператор return и выйду из функции?' — спрашивают они. Отличный вопрос! Это одна из любимых тем на технических собеседованиях для Python-разработчиков. Давайте разберем этот нюанс максимально подробно. В Python интерпретатор отслеживает стек вызовов и контексты выполнения. Если внутри блока try встречается оператор return, интерпретатор не просто слепо прыгает наружу. Он видит: 'Ага, мы находимся внутри конструкции try, у которой есть блок finally. Я не могу просто так уйти. Я должен запомнить значение, которое нужно вернуть, приостановить выход, выполнить код в блоке finally, и только после этого завершить функцию и отдать возвращаемое значение'.

Это фундаментальное свойство языка. То же самое касается операторов управления циклами: break и continue. Если блок try находится внутри цикла for или while, и внутри try срабатывает break (команда немедленно прервать цикл), интерпретатор сначала выполнит блок finally для текущей итерации, и только потом прервет цикл. Это гарантирует, что механизм управления ресурсами невозможно обойти случайным выходом из функции или цикла. Это создает невероятно надежный фундамент для построения отказоустойчивых систем. Даже если в блоке try возникает исключение, которое не перехватывается ни одним блоком except (то есть программа обречена на падение), интерпретатор прямо в процессе 'падения' и раскручивания стека (stack unwinding) остановится, выполнит код в finally, и продолжит 'падать' дальше, выводя Traceback. Единственные ситуации, когда finally не выполнится — это жесткое завершение процесса на уровне ОС (например, kill -9 в Linux, выдергивание шнура питания из сервера) или вызов функции os._exit(), которая мгновенно убивает интерпретатор в обход всех стандартных механизмов.

python
# Демонстрация мощи finally при досрочном выходе (return)

def test_finally_with_return():
    print("--- Запуск функции ---")
    try:
        print("Выполнение блока try")
        return "Значение из try"
    finally:
        print("Выполнение блока finally (Даже после return!)")

result = test_finally_with_return()
print(f"Функция вернула: {result}")

# Вывод в консоль будет таким:
# --- Запуск функции ---
# Выполнение блока try
# Выполнение блока finally (Даже после return!)
# Функция вернула: Значение из try

Что произойдет, если внутри блока `try` выполнится оператор `return`, а у конструкции есть блок `finally`?

Ловушка: Return внутри finally

Хотя мы выяснили, что finally выполняется всегда, есть один экстремально опасный антипаттерн, о котором вы обязаны знать на уровне Intermediate. Что произойдет, если вы поместите оператор return внутри самого блока finally? Представьте: в блоке try возникает ошибка ZeroDivisionError. Интерпретатор начинает процедуру падения. Как положено, он заходит в блок finally, чтобы прибраться перед тем как показать ошибку пользователю. И вдруг внутри finally он встречает оператор return "Всё ок". Что сделает Python?

В Python блок finally имеет наивысший приоритет. Если finally содержит оператор return, break или continue, он подавляет и уничтожает любое исключение, которое в данный момент находилось в процессе обработки! В нашем примере ZeroDivisionError просто исчезнет в никуда, программа не упадет, а функция спокойно вернет строку 'Всё ок'. С точки зрения вызывающего кода функция отработала идеально, хотя внутри произошла критическая ошибка. Это катастрофа для отладки. Логи будут молчать, ошибки не будет, но данные в приложении будут повреждены или некорректны. Точно так же, если в блоке try был свой return "Результат", а в finally есть return "Перезаписано", функция вернет 'Перезаписано'. Золотое правило: Никогда не используйте return, break или continue внутри блока finally. Блок finally предназначен исключительно для 'побочных эффектов' — закрытия файлов, коммитов в БД, вывода логов очистки, но не для изменения потока управления (control flow) самой функции.

Полная архитектура: try - except - else - finally

Теперь мы готовы собрать все элементы воедино. Конструкция try-except-else-finally — это полная форма механизма обработки исключений. Порядок следования блоков строго регламентирован синтаксисом Python: сначала идет try (обязательно один), затем от нуля до множества блоков except (для разных типов ошибок), затем ровно один блок else (необязательный, выполняется только при отсутствии ошибок в try), и завершает все это ровно один блок finally (необязательный, выполняется всегда). Давайте проговорим алгоритм выполнения этой сложной структуры, словно мы и есть сам интерпретатор Python.

Шаг 1: Выполняем код внутри try. Если всё прошло успешно, переходим к Шагу 2. Если возникла ошибка, переходим к Шагу 3. Шаг 2 (Успех): Игнорируем все except. Заходим в else и выполняем его код. Затем переходим к Шагу 4. Шаг 3 (Ошибка): Ищем подходящий except. Если нашли — выполняем код внутри этого except. Блок else полностью игнорируется. Если подходящего except нет (ошибка не перехвачена), ошибка остается 'активной'. Шаг 4: Выполняем код внутри finally в любом случае. Шаг 5: Если ошибка оставалась 'активной' (не перехваченной), выбрасываем ее наружу, заставляя программу упасть. Если всё было обработано штатно, продолжаем выполнение программы со строки, следующей после всей этой большой конструкции. Это выглядит как сложный танец, но как только вы поймете логику маршрутизации, ваш код станет невероятно предсказуемым и безопасным.

python
def process_transaction(db_connection, data):
    """
    Полный цикл обработки транзакции с использованием всех блоков.
    """
    print("--- Начало транзакции ---")
    try:
        # ОПАСНАЯ ЗОНА: попытка записи в базу
        print("Попытка записи данных в БД...")
        db_connection.insert(data) # Может вызвать DBConnectionError или DataFormatError
        
    except DBConnectionError as e:
        # ОБРАБОТКА ОШИБОК: пропала сеть или упал сервер БД
        print(f"Критическая ошибка сети: {e}")
        db_connection.rollback() # Откат неудачной попытки
        
    except DataFormatError as e:
        # ОБРАБОТКА ОШИБОК: неверный формат данных
        print(f"Ошибка валидации данных: {e}")
        # Здесь откат может не требоваться, просто логируем
        
    else:
        # ПУТЬ УСПЕХА: выполняется только если insert прошел без ошибок
        print("Данные успешно записаны.")
        db_connection.commit() # Подтверждаем транзакцию
        send_notification_to_user("Транзакция успешна!") # Если тут ошибка, она упадет явно
        
    finally:
        # ОЧИСТКА: выполняется всегда
        print("Закрытие соединения с БД (в любом случае).")
        db_connection.close()
        print("--- Конец операции ---")
Блок Когда выполняется? Основное назначение
try Всегда первый (обязателен) Размещение рискованного кода, который может вызвать исключение.
except При возникновении ошибки в `try` Перехват и обработка конкретных типов исключений, восстановление работы.
else Если в `try` НЕ было ошибок Выполнение кода, зависящего от успеха `try` (чтобы не перегружать `try`).
finally Всегда, при любых условиях Гарантированная очистка ресурсов, освобождение памяти, закрытие файлов.

Какой блок в конструкции обработки исключений должен содержать код для закрытия сетевого сокета или файла, чтобы это произошло гарантированно?

Project-Based Learning: Симуляция работы с API и файловой системой

Давайте закрепим теорию на практике (Project-Based Learning). Представьте, что вы разрабатываете скрипт для автоматического скачивания отчетов из внешней API системы и сохранения их на диск. Это классическая задача для Python. В процессе могут возникнуть три типа проблем: 1) Нет интернета (сетевая ошибка). 2) API вернуло некорректные данные (ошибка JSON). 3) У нас нет прав на запись файла на диск (ошибка файловой системы). Мы должны написать отказоустойчивую функцию, которая корректно обработает эти ситуации, используя все 4 блока (try-except-else-finally). В нашем проекте мы создадим 'моки' (имитации) этих систем, чтобы протестировать логику.

Архитектура нашего решения будет следующей. В блоке try мы выполним только скачивание данных и их парсинг. В блоках except мы перехватим специфические ошибки (например, ConnectionError и ValueError). В блоке else, зная, что данные получены успешно, мы попытаемся записать их на диск. И в блоке finally мы запишем в лог-систему общее время выполнения операции и факт ее завершения, независимо от того, скачали мы отчет или программа упала с ошибкой. Такой подход демонстрирует архитектуру уровня 'Production'. Мы не смешиваем сетевую логику с файловой логикой в одном месте, и мы гарантируем, что аудит (логирование) произойдет в любом случае.

Задание

Практическое задание: Создание надежного загрузчика данных. Реализуйте структуру функции, которая имитирует загрузку.

  • Определите функцию `download_and_save_report(url, filepath)`.
  • Напишите блок `try`, в котором вызывается гипотетическая функция `fetch_data(url)`.
  • Добавьте блок `except ConnectionError`, чтобы выводить 'Ошибка сети'.
  • Добавьте блок `else`, в котором данные записываются в файл (используйте фиктивный `write_to_file`).
  • Добавьте блок `finally`, выводящий 'Операция скачивания завершена'.
10 баллов

Разбор проекта и связь с Context Managers (with)

Реализуя предыдущее задание, вы могли заметить один важный момент. Когда мы пишем код для работы с файлами, в современном Python мы чаще всего используем конструкцию with open(filepath) as f:. Это называется Контекстным Менеджером (Context Manager). Почему это важно в контексте нашего урока? Потому что оператор with под капотом реализован именно через механизм try-finally! В ранних версиях Python (до 2.5) разработчикам приходилось каждый раз писать громоздкие блоки try-finally только для того, чтобы безопасно открыть и закрыть файл. Это было утомительно и нарушало принцип DRY (Don't Repeat Yourself).

Контекстные менеджеры инкапсулируют логику finally внутри себя. Когда вы пишете with open(...), Python автоматически гарантирует, что при выходе из блока with (неважно, успешно или из-за ошибки) у объекта файла будет вызван специальный магический метод __exit__, который содержит инструкцию file.close(). Значит ли это, что блок finally больше не нужен? Абсолютно нет! Оператор with отлично подходит для стандартных объектов (файлы, локи потоков), но когда вы пишете свою уникальную бизнес-логику, управляете сложными состояниями или работаете со старыми библиотеками, не поддерживающими протокол контекстных менеджеров, конструкция try-finally остается вашим единственным и самым надежным инструментом. Знание того, как работает finally, дает вам понимание того, как работает with 'под капотом'.

Какая конструкция в современном Python часто используется для автоматического управления ресурсами (например, файлами) и скрывает под капотом логику `try-finally`?

Вложенные блоки try-except

Еще один продвинутый сценарий, с которым вы столкнетесь в реальных проектах, — это вложенность конструкций обработки исключений. Ничто не запрещает вам поместить один блок try-except внутрь другого блока try, except, else или finally. Зачем это нужно? Представьте, что внутри блока finally, где мы хотим закрыть сетевое соединение, происходит ошибка! Например, мы пытаемся закрыть сокет, но сервер на другой стороне уже жестко разорвал соединение, и вызов метода close() выбрасывает SocketError. Если мы не перехватим эту ошибку внутри finally, она перезапишет исходную ошибку из блока try, и мы потеряем причину первоначального сбоя.

Для защиты от таких ситуаций, код внутри finally (или except) оборачивается в собственный мини-блок try-except. Выглядит это так:
finally:
    try:
        connection.close()
    except Exception as e:
        log.error(f'Ошибка при закрытии соединения: {e}').
Такая архитектура гарантирует, что процесс 'очистки' сам по себе безопасен и не станет причиной нового, маскирующего 'краша'. Python 3 также предоставляет мощный механизм 'Exception Chaining' (цепочки исключений). Если во время обработки одного исключения возникает другое, Python выведет оба в Traceback с пометкой 'During handling of the above exception, another exception occurred'. Это невероятно помогает при отладке вложенных структур.

Резюме: Философия надежного кода

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

Золотые правила джедая Python:
1. Держите блок try максимально коротким. 'Оборачивайте' только тот вызов, который по документации может выбросить исключение. Чем больше кода в try, тем выше шанс словить 'левую' ошибку.
2. Перехватывайте только конкретные ошибки. except Exception: используйте только на самом верхнем уровне приложения (глобальный логгер), никогда не используйте его в бизнес-логике, чтобы не скрыть опечатки.
3. Используйте else для логики 'Пути успеха'. Если действие Б должно произойти только если действие А удалось — смело кладите действие Б в else.
4. Используйте finally для освобождения ресурсов (порты, файлы, БД). Помните, что finally переживет даже return и break.
5. Никогда не используйте return, break или continue внутри finally, чтобы не проглотить легитимные исключения.
Освоив эти правила, вы сможете уверенно проходить код-ревью в любой IT-компании. В следующих уроках мы будем применять эти знания при разработке собственных классов и ООП-архитектуры.