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

EAFP вместо LBYL: почему Python поощряет 'сделать и обработать ошибку', а не 'проверить всё заранее'

core переписать код с предварительными проверками (if os.path.exists, if key in dict) в try/except там, где это идиоматичнее и безопаснее от race condition

C-программист приучен думать в терминах предохранителей: перед strcpy проверить длину, перед разыменованием указателя проверить на NULL, перед open() проверить access(). Это разумно в языке, где ошибка — это undefined behavior или падение процесса. Такой стиль называется LBYL — Look Before You Leap: сначала выясняем, безопасно ли действие, потом действуем.

Python поощряет обратную модель — EAFP, Easier to Ask Forgiveness than Permission: сначала выполняем действие, а если оно невозможно, интерпретатор сообщает об этом исключением, которое мы перехватываем. Исключения в Python — стандартный и основной механизм сообщения об ошибках, и EAFP опирается на две особенности языка, которых нет в C:

  1. Динамическая/утиная типизация. Типы в Python привязаны к объектам и проверяются во время выполнения, а пригодность объекта для операции определяется наличием нужных методов, а не классом. Проверить это заранее в общем случае невозможно — isinstance-проверки на каждый шаг превращают Python в плохую копию C с ручной типизацией.
  2. TOCTOU (time-of-check to time-of-use). Между проверкой и действием проходит время, за которое состояние мира могло измениться — файл удалили, ключ словаря убрал другой поток, сокет отвалился. Проверка создаёт ложное чувство безопасности, а не устраняет проблему.

Классическая ошибка си-программиста:

import os

if os.path.exists(path):
    with open(path) as f:
        data = f.read()

Между os.path.exists и open файл может быть удалён другим процессом — это ровно тот же класс гонки, из-за которого проверку прав через access() перед open() в C считают небезопасной. Идиоматичный Python:

try:
    with open(path) as f:
        data = f.read()
except FileNotFoundError:
    data = None

Здесь нет окна гонки: попытка и обработка её результата — одно действие с точки зрения кода.

То же со словарями. Инстинкт C-программиста — «проверить границы перед доступом»:

if key in d:
    value = d[key]
else:
    value = default

Это работает, но делает два обращения к хеш-таблице вместо одного и остаётся LBYL-мышлением. Идиоматичнее:

value = d.get(key, default)

а если нужна ветка с побочным эффектом — try/except KeyError. dict.get — не «трюк», а прямое следствие EAFP: спросить объект напрямую надёжнее, чем гадать заранее.

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

Задача: получить конфигурацию пользователя из вложенного словаря, где часть ключей может отсутствовать, а часть — быть не тем типом.

LBYL-версия в стиле C:

def get_timeout(config):
    if "network" in config:
        network = config["network"]
        if isinstance(network, dict) and "timeout" in network:
            timeout = network["timeout"]
            if isinstance(timeout, (int, float)):
                return timeout
    return 30

Четыре уровня проверок повторяют структуру данных вручную — ровно то, что в C вы делали бы, проверяя каждый указатель перед разыменованием. EAFP-версия:

def get_timeout(config):
    try:
        return config["network"]["timeout"]
    except (KeyError, TypeError):
        return 30

TypeError здесь перехватывает случай, когда config["network"] — не словарь (например, строка: индексация строки по строковому ключу даёт TypeError), а KeyError — случай отсутствующего ключа. Одна строка вместо дерева проверок, и она устойчива к любой форме «не такой структуры», а не только к предусмотренным случаям.

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

Возьмите функцию, читающую значение по цепочке из списка (по индексу) внутри словаря:

def get_first_score(data, key):
    if key in data and isinstance(data[key], list) and len(data[key]) > 0:
        return data[key][0]
    return None

Перепишите её в стиле EAFP одним try/except, перехватывая ровно те исключения, которые реально могут возникнуть (KeyError, IndexError, TypeError). Проверьте на data = {"scores": []}, data = {"scores": [10, 20]} и data = {}.

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

Вы поняли идею, если новая версия перехватывает исключения по конкретным типам (не except Exception), не делает предварительных проверок структуры и даёт тот же результат на всех трёх тестовых входах, что и LBYL-версия.

flowchart TB
    subgraph LBYL["LBYL: проверка заранее"]
        A1[Проверить: файл существует?] --> A2{Существует}
        A2 -->|окно гонки| A3[Другой процесс удаляет файл]
        A3 --> A4[open всё равно падает с ошибкой]
    end
    subgraph EAFP["EAFP: сделать и обработать"]
        B1[Попытаться открыть файл] --> B2{Успех?}
        B2 -->|да| B3[Работать с файлом]
        B2 -->|нет| B4[except FileNotFoundError]
    end
LBYL оставляет окно гонки между проверкой и действием, EAFP обрабатывает результат самой попытки

Вывод

Проверка «на всякий случай» перед действием в Python обычно не устраняет ошибку, а дублирует работу интерпретатора и оставляет гонку. Действие — это и есть проверка; исключение — это её отрицательный результат. dict.get/try-except — не сахар, а прямое выражение EAFP.

AI-generated · source-grounded review

🛡 Fact-checked: 1 risky claim verified · 2 removed · confidence: high · figures: 1
[verified] LBYL и EAFP — два стиля обработки ошибок; Python поощряет EAFP
Согласуется с базой знаний: исключения — основной механизм ошибок в Python, утиная типизация проверяется в runtime
[unconfirmed by second model] Проверка os.path.exists перед open оставляет окно гонки TOCTOU
Логически корректно и не противоречит базе знаний
Second model: KB не упоминает TOCTOU или гонки при проверке существования файлов.
[softened] В POSIX существуют предупреждения про TOCTOU из-за access()+open()
Конкретная ссылка на POSIX отсутствует в базе знаний; переформулировано как общее замечание о небезопасности такой проверки
[softened] try/open — атомарное действие
Атомарность на уровне ОС не подтверждается; сказано «одно действие с точки зрения кода»
[unconfirmed by second model] if key in d + d[key] делает два обращения к хеш-таблице, dict.get — одно
dict — встроенная хеш-таблица (база знаний); утверждение о числе обращений корректно
Second model: KB описывает dict как хеш-таблицу, но не сравнивает число обращений при in/get.
[unconfirmed by second model] Индексация строки строковым ключом даёт TypeError
Стандартное поведение Python, не противоречит базе знаний
Second model: KB не упоминает индексацию строк строковыми ключами и TypeError.
[unconfirmed by second model] Фигура: LBYL оставляет окно гонки, EAFP обрабатывает результат попытки
Метки и направления стрелок согласуются с текстом и базой знаний
Second model: KB не содержит описаний диаграмм или сравнения LBYL/EAFP в виде схем.
[removed] Утверждение о том, что POSIX содержит специальные предупреждения про TOCTOU из-за access()+open(), смягчено до общего утверждения о небезопасности такой проверки — точная формулировка отсутствует в базе знаний
[removed] Формулировка про 'атомарное действие' у try/open заменена на 'одно действие с точки зрения кода': атомарность на уровне ОС базой знаний не подтверждается

A second model re-checked this lesson's claims and corroborated 1 of 7 , and could not confirm 6 either way. This is incomplete corroboration, not a disagreement.

Key concepts: EAFP LBYL control flow через исключения
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. Почему проверка os.path.exists() перед open() не устраняет ошибку полностью?
2. Что из перечисленного лучше всего описывает стиль LBYL?
3. Почему EAFP естественно сочетается с утиной типизацией Python?
On your own course, these are marked as you answer