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 — проверить, потом действовать:
user = {'name': 'Анна', 'age': 25}
if 'email' in user:
email = user['email']
else:
email = 'не указан'
print('LBYL:', email)
LBYL: не указан
Та же задача взглядом 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 — чтение с умолчанием, при котором не нужны ни проверка, ни исключение. Это не победа одного стиля над другим, а напоминание: готовые методы с умолчанием часто решают вопрос до того, как он стал выбором стиля. Но такие методы есть не у всего: у атрибутов объекта, ключей файла, элементов ввода — нет.
| LBYL | EAFP | |
|---|---|---|
| Идея | сначала проверить, потом действовать | сначала действовать, сбой — ловить |
| Инструменты | in, hasattr, isdigit, os.path.exists | try и 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))
1. Что означает аббревиатура EAFP?
2. Что означает аббревиатура LBYL?
3. Почему LBYL-проверка не гарантирует успех действия?
4. Какой стиль считается более идиоматичным в Python?
5. Куда отнести user.get('email', 'не указан')?
6. В чём опасность EAFP, выполненного как try вокруг большого куска кода с except Exception?
Реши одну задачу двумя стилями. Дан словарь data = {'name': 'Омск'}, ключа city в нём нет. Сначала проверкой in напечатай «LBYL: {город}» или «LBYL: нет». Затем через try с except KeyError напечатай «EAFP: {город}» или «EAFP: нет».
Что такое 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-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Исключения · Урок 11
KeyError в Python: ошибки словарей
Словарь не нашёл ключ — и честно сказал об этим KeyError. Разбираем объект ошибки, читаем из него ключ и выбираем между get, in и честным исключением.
Исключения · Урок 16
Ошибки в цикле Python: try внутри for и продолжение после сбоя
Сто строк данных, одна битая — и без защиты падает весь проход. Ставим try вокруг одного элемента: сбой обрабатывается, цикл идёт дальше, сбои считаются и попадают в журнал.
Исключения · Урок 19
except или if в Python: граница применимости проверки и исключения
Отсутствие ключа: проверить if-ом или поймать KeyError? Разбираем честную границу: обычные варианты — if, редкие сбои — except, и случай, где проверка проигрывает исключению всухую.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Словари · Урок 7
in: проверка ключа в словаре
Оператор in отвечает на вопрос «есть ли такой ключ»: проверка перед доступом вместо KeyError, not in, грабли с проверкой значений и причина скорости поиска.
проверка ключа перед доступомin проверяет только ключи словаря
Словари · Урок 6
Удаление: del, pop и popitem
Четыре способа убрать данные из словаря: del молча удаляет пару, pop отдаёт значение, popitem снимает последнюю пару, clear чистит всё. Плюс разбор, почему удалять во время обхода нельзя.
удалить элемент из словаря pythonудаление ключа из словаря
Исключения · Урок 18
assert в Python: проверки для разработчика и AssertionError
assert — это заявление: здесь должно быть так. Провал превращается в AssertionError с вашим сообщением, а флаг -O умеет вырезать все проверки разом — поэтому инструмент разработчика, а не валидатор ввода.
assert против raise python