UpPetto

This is a real course generated by UpPetto — unedited.

AI-generated. Two models wrote it, a third checked every risky claim against sources, a fourth re-checked.

Create my own course

try/except/finally на практике: конкретные исключения вместо errno-стиля и bare except

core написать обработку ошибок для операции ввода-вывода, которая перехватывает только ожидаемые исключения и гарантированно освобождает ресурс

В C очистка ресурсов размазана по коду через goto cleanup и повторяющиеся if (err) { free(...); return -1; } на каждом уровне — это способ гарантировать, что fclose/free вызовутся на всех путях выхода из функции. Python убирает эту ручную бухгалтерию через try/except/finally и контекстные менеджеры (with), но перенос C-привычек сюда рождает две характерные ошибки.

Ошибка первая: голый except:. Си-программист привык, что «поймать всё» — это safety net, аналог проверки if (ret != 0) без разбора кода ошибки. В Python except: без указания типа перехватывает всё, что наследуется от BaseException — включая KeyboardInterrupt (Ctrl+C) и SystemExit (вызов sys.exit()). Программа, которая не реагирует на Ctrl+C и не может корректно завершиться по sys.exit(), — прямое следствие этой привычки. Чуть менее опасный, но всё равно вредный вариант — except Exception: без разбора: он маскирует программные ошибки (TypeError, AttributeError из-за опечатки) точно так же, как в C маскирует баг привычка писать if (ret) { /* просто игнорируем */ }.

Ошибка вторая: считать, что finally — это goto cleanup, поэтому можно не думать о том, какие исключения вообще возможны. finally гарантирует выполнение блока при любом исходе — исключение, return, break — но это не индульгенция на то, чтобы не указывать конкретные типы в except. finally отвечает за освобождение, except — за решение, что делать с конкретной ошибкой. Это разные обязанности, и в C они смешаны в одном if.

Структура для операции ввода-вывода, написанная правильно:

resource = acquire_resource()
try:
    result = do_io(resource)
except (ConnectionError, TimeoutError) as e:
    log.error("I/O failed: %s", e)
    raise
finally:
    resource.release()

Обратите внимание: finally выполнится и при успехе, и при перехваченном исключении, и даже если except-блок сам решит поднять исключение повторно (raise без аргумента передаёт оригинальное исключение дальше с сохранённым traceback — чего errno-стиль вообще не умеет).

Важный нюанс, которого нет в C: except-блоки проверяются сверху вниз, и перехватывается первый подходящий по иерархии тип. Значит except Exception перед except ValueError в том же блоке сделает второй недостижимым — молчаливая логическая ошибка, которую интерпретатор не подсветит.

Разобранный пример

Задача: прочитать JSON-конфиг из ресурса, обработать отсутствие данных и повреждённый JSON отдельно, и в любом случае освободить ресурс (разберём случай, когда ресурс — не файл с with, а, скажем, сетевое соединение).

import json

def load_config(path):
    conn = acquire_connection(path)  # условный "ресурс", не файл
    try:
        raw = conn.read_all()
        return json.loads(raw)
    except FileNotFoundError:
        log.warning("config %s not found, using defaults", path)
        return {}
    except json.JSONDecodeError as e:
        log.error("config %s is corrupted: %s", path, e)
        raise
    finally:
        conn.close()

Рассуждение: FileNotFoundError и json.JSONDecodeError — это два разных по смыслу отказа с разной реакцией (один можно тихо заглушить дефолтом, другой нужно поднять дальше, потому что повреждённый конфиг молча игнорировать нельзя). conn.close() в finally выполнится в обоих случаях и при успехе — ровно так, как в C вы гарантировали бы fclose через единственный goto cleanup, но здесь это встроено в структуру языка и не размножается по функции.

Попробуй сейчас

У вас есть функция, которая открывает сетевой сокет (условно sock = open_socket(addr)), отправляет запрос (sock.send(data)) и читает ответ (sock.recv()). Возможные ошибки: ConnectionRefusedError при подключении, TimeoutError при чтении ответа. Напишите обработку так, чтобы:

  • оба типа ошибок логировались с разным сообщением;
  • сокет закрывался в любом случае, включая случай, когда ошибка не входит ни в один из двух типов (например, KeyboardInterrupt);
  • неожиданные исключения не проглатывались.

Получилось, если…

Проверьте себя: в вашем коде нет except: и нет except Exception: без последующего raise; sock.close() вызывается ровно один раз в finally; если временно закомментировать оба except и подставить искусственный ValueError, программа падает с трейсбеком, а не тихо продолжает работу.

flowchart TD
    Base[BaseException] --> Exc[Exception]
    Base --> KI[KeyboardInterrupt]
    Base --> SE[SystemExit]
    Exc --> OSE[OSError]
    Exc --> VE[ValueError]
    Exc --> TE[TypeError]
    OSE --> FNF[FileNotFoundError]
    OSE --> TO[TimeoutError]
    OSE --> CE[ConnectionError]
    CE --> CR[ConnectionRefusedError]
    Bare[bare except: перехватывает всё дерево] -.-> Base
голый except перехватывает всё дерево, включая KeyboardInterrupt и SystemExit, которые не являются программными ошибками

Вывод

except: без типа — это не «поймать все ошибки», а «поймать даже сигналы завершения программы». Указывайте конкретные типы исключений, оставляйте finally только для освобождения ресурсов, и не путайте порядок except-блоков — он проверяется последовательно, а не по «наиболее подходящему» типу.

AI-generated · source-grounded review

🛡 Fact-checked: 0 risky claims verified · 1 removed · confidence: high · figures: 1
[unconfirmed by second model] bare except перехватывает всё, наследуемое от BaseException, включая KeyboardInterrupt и SystemExit
Соответствует стандартной иерархии исключений Python
Second model: KB обрывается до раздела о bare except и не упоминает BaseException, KeyboardInterrupt или SystemExit.
[unconfirmed by second model] finally выполняется при любом исходе try-блока (исключение, return, break)
Стандартная семантика try/except/finally
Second model: KB не описывает поведение блока finally при различных исходах.
[unconfirmed by second model] except-блоки проверяются сверху вниз; except Exception перед except ValueError делает второй недостижимым
Соответствует семантике языка
Second model: KB не упоминает порядок проверки except-блоков сверху вниз.
[unconfirmed by second model] raise без аргумента перевыбрасывает исходное исключение с сохранённым traceback
Стандартное поведение; база знаний подчёркивает исключения как основной механизм ошибок
Second model: KB не описывает поведение raise без аргументов и сохранение traceback.
[removed] Фигура иерархии: OSError → ConnectionRefusedError напрямую
Некорректная связь: ConnectionRefusedError наследуется от ConnectionError; в схему добавлен промежуточный узел ConnectionError (фигура сохранена, связь исправлена)
[unconfirmed by second model] Фигура иерархии: BaseException → Exception/KeyboardInterrupt/SystemExit, Exception → OSError/ValueError/TypeError, OSError → FileNotFoundError/TimeoutError
Соответствует встроенной иерархии исключений Python
Second model: KB не описывает иерархию BaseException/Exception/OSError и их потомков.
[removed] В схеме иерархии исправлена связь OSError → ConnectionRefusedError: добавлен промежуточный класс ConnectionError, от которого ConnectionRefusedError наследуется напрямую

⚑ Second opinion disputes this lesson

A second, independent model re-checked the fact-check and disagreed on at least one point below. Do not read this lesson as fully verified.

[contradicted] В примере lesson 2 с сетевым соединением (conn) перехватывается FileNotFoundError, хотя сетевое соединение не работает с файловой системой и не может вызвать это исключение.
Текст явно указывает, что ресурс — не файл, но затем ловит файловое исключение, что логически противоречиво.
Raised by the second model only, not the original fact-check.
Key concepts: try/except/finally bare except антипаттерн конкретные исключения
Tell me more 🔒 Didn't understand — explain simply 🔒 Show examples 🔒 Sources 🔒

On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course

Check yourself

1. Чем опасен голый except: по сравнению с except Exception:?
2. Что гарантированно выполняется в блоке finally?
3. Что произойдёт, если в цепочке except сначала стоит except Exception, а затем except ValueError?
On your own course, these are marked as you answer