Перейти к содержанию

432 уроков, 26 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 14 из 20 Средний 15 мин 100 XP

EAFP против LBYL в Python: два стиля проверки

Look Before You Leap — смотри, прежде чем прыгнуть; Easier to Ask Forgiveness than Permission — проще извиниться, чем спросить. Одна задача, два стиля, один вердикт.

Редакция Питоники

В английском словаре питониста живут две аббревиатуры. LBYL — Look Before You Leap, «посмотри, прежде чем прыгнуть»: перед опасным действием проверь условия. EAFP — Easier to Ask Forgiveness than Permission, «проще просить прощения, чем разрешения»: действуй, а если не вышло — извиняйся через except. Это два взгляда на одну и ту же задачу, и в этом уроке они решат её плечом к плечу.

Одна задача двумя стилями

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

LBYL: проверка перед действием
user = {'name': 'Анна', 'age': 25}

if 'email' in user:
    email = user['email']
else:
    email = 'не указан'
print('LBYL:', email)
Вывод
LBYL: не указан

Та же задача взглядом EAFP — сначала действие, сбой ловим:

EAFP: действие с ловлей
user = {'name': 'Анна', 'age': 25}

try:
    email = user['email']
except KeyError:
    email = 'не указан'
print('EAFP:', email)
Вывод
EAFP: не указан

Вывод одинаковый до буквы. Разница — в устройстве пути: LBYL сначала отвечает на вопрос о мире («есть ли ключ?»), потом делает шаг; EAFP делает шаг и разбирается с последствиями. Проверок может быть много, а исключение одно — и от этого зависит, какой стиль читается легче.

Деление: стиль видно в чистом виде

две функции с одинаковым поведением
def lbyl_divide(a, b):
    if b != 0:
        return a / b
    return None

def eafp_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None

print(lbyl_divide(10, 2))
print(lbyl_divide(10, 0))
print(eafp_divide(10, 2))
print(eafp_divide(10, 0))
Вывод
5.0
None
5.0
None

Обе функции возвращают одно и то же на обоих входах: пять и None. LBYL-версия держит в голове второе условие — «а что, если ноль»; EAFP-версия описывает только нормальный путь, а ноль живёт одной строкой в except. Заметьте: проверка b != 0 и исключение ZeroDivisionError — это одно и то же условие, записанное дважды в жизни программы: один раз глазами, один раз Python'ом.

Третий путь: get

словарь дарит готовое умолчание
user = {'name': 'Анна'}

email = user.get('email', 'не указан')
print('get:', email)
Вывод
get: не указан

Для словарей из одиннадцатого урока существует третий ответ: метод get — чтение с умолчанием, при котором не нужны ни проверка, ни исключение. Это не победа одного стиля над другим, а напоминание: готовые методы с умолчанием часто решают вопрос до того, как он стал выбором стиля. Но такие методы есть не у всего: у атрибутов объекта, ключей файла, элементов ввода — нет.

LBYLEAFP
Идеясначала проверить, потом действоватьсначала действовать, сбой — ловить
Инструментыin, hasattr, isdigit, os.path.existstry и except
Слабое местопроверка и действие раздельны: между ними состояние может изменитьсяширокий except прячет настоящие баги
Читаемостьлес if при многих условияхнормальный путь без ветвлений
данные с пропущенными полями
records = [
    {'id': 1, 'score': 90},
    {'id': 2},
    {'id': 3, 'score': 70},
]

for rec in records:
    try:
        print('участник', rec['id'], '- балл', rec['score'])
    except KeyError:
        print('участник', rec['id'], '- балла нет')
Вывод
участник 1 - балл 90
участник 2 - балла нет
участник 3 - балл 70

На потоке записей с пропущенными полями EAFP выигрывает просто по объёму кода: одна строка нормального пути, одна строка исключения. LBYL-версия этой петли выросла бы до проверки каждого ключа и вложенных веток. Обычный путь в Python — счастливый: try без срабатывания почти ничего не стоит, и код читается по основному сценарию.

Какой стиль выбрать в цикле по сомнительным данным и где проходит граница с обычными if — темы соседних уроков: ошибки в циклах и except против if. А пока держите правило большого пальца: в Python чаще пишут EAFP — действуют и ловят конкретное исключение, оставляя проверки там, где вопрос о мире сам по себе важен.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

def a(d):
    return d.get('k', 0)

def b(d):
    try:
        return d['k']
    except KeyError:
        return 0

print(a({}), b({}), a({'k': 3}), b({'k': 3}))
x = {'a': 1}
try:
    print(x['b'])
except KeyError:
    print('EAFP')
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None

print(divide(8, 2), divide(8, 0))
Проверь себя
0 / 6

1. Что означает аббревиатура EAFP?

2. Что означает аббревиатура LBYL?

3. Почему LBYL-проверка не гарантирует успех действия?

4. Какой стиль считается более идиоматичным в Python?

5. Куда отнести user.get('email', 'не указан')?

6. В чём опасность EAFP, выполненного как try вокруг большого куска кода с except Exception?

Карточки терминов
Запомнено: 0 / 7
Практика

Реши одну задачу двумя стилями. Дан словарь data = {'name': 'Омск'}, ключа city в нём нет. Сначала проверкой in напечатай «LBYL: {город}» или «LBYL: нет». Затем через try с except KeyError напечатай «EAFP: {город}» или «EAFP: нет».

practice.py
Вопросы и ответы по уроку

Что такое EAFP в Python?

Easier to Ask Forgiveness than Permission — идиома «действуй и лови»: код сразу пытается выполнить операцию, а сбой обрабатывает в except конкретного типа. Например: try: user['email'] except KeyError: email = 'не указан'. Считается основным стилем Python.

Урок 11: KeyError — ошибки словарей

Что такое LBYL и когда его использовать?

Look Before You Leap — «сначала проверь»: перед действием идут условия вроде if 'email' in user или os.path.exists(path). Уместен, когда вопрос о состоянии важен сам по себе — показать пользователю наличие файла до действия или ветвиться по нескольким условиям.

Урок 19: except против if — граница применимости

Чем опасен стиль LBYL?

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

Как правильно применять EAFP?

Оборачивать конкретное действие и ловить конкретное исключение: try вокруг чтения ключа с except KeyError, вокруг деления — с ZeroDivisionError. Широкая ловля except Exception вокруг половины программы — это уже не идиома, а глушитель ошибок, который прячет баги.

Урок 15: except Exception — когда ловить всё

Понравился урок? Сошлитесь на него

«Обычный путь в Python — счастливый: try без срабатывания почти ничего не стоит, и код читается по основному сценарию.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.