LBYL против EAFP: Предотвращение или обработка ошибок в Python
Оглавление
- Ошибки и исключительные ситуации: их предотвращение или устранение?
- Стиль “Посмотри, прежде чем прыгнуть” (LBYL)
- Стиль “Легче попросить прощения, чем разрешения” (EAFP)
- Правильный путь на языке Python: LBYL или EAFP?
- Стили кодирования LBYL и EAFP в Python
- Общие проблемы с LBYL и EAFP
- EAFP против LBYL С примерами
- Заключение
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Обработка или предотвращение ошибок в Python: LBYL против EAFP
Устранение ошибок и исключительных ситуаций является распространенным требованием в программировании. Вы можете либо предотвращать ошибки до того, как они произойдут, либо обрабатывать ошибки после того, как они произошли. В общем, у вас будет два стиля кодирования, соответствующих этим стратегиям: посмотри, прежде чем прыгнуть (LBYL) и легче просить прощения, чем разрешения (EAFP), соответственно. В этом руководстве вы познакомитесь с вопросами и соображениями, связанными с LBYL и EAFP в Python.
Изучив стили кодирования Python LBYL и EAFP, вы сможете решить, какую стратегию и стиль кодирования использовать при работе с ошибками в вашем коде.
В этом руководстве вы узнаете, как:
- Используйте стили LBYL и EAFP в вашем коде на Python
- Понять плюсы и минусы LBYL против EAFP
- Решите, когда использовать либо LBYL, либо EAFP
Чтобы извлечь максимальную пользу из этого руководства, вы должны быть знакомы с тем, как работают условные операторы и try ... except. Эти два утверждения являются строительными блоками для реализации стилей кодирования LBYL и EAFP в Python.
Бесплатный бонус: 5 Размышления о мастерстве владения Python, бесплатный курс для разработчиков Python, который показывает вам план действий и мышление, с которым вы будете работать. вам нужно поднять свои навыки работы с Python на новый уровень.
Ошибки и исключительные ситуации: их предотвращение или обработка?
Работа с ошибками и исключительными ситуациями является фундаментальной частью компьютерного программирования. Ошибки и исключения встречаются повсюду, и вам нужно научиться управлять ими, если вы хотите получить надежный код.
Вы можете следовать, по крайней мере, двум общим стратегиям, когда дело доходит до работы с ошибками и исключениями:
- Предотвращать возникновение ошибок или исключительных ситуаций
- Обрабатывать ошибки или исключительные ситуации после их возникновения
Исторически сложилось так, что предотвращение ошибок до того, как они произойдут, было наиболее распространенной стратегией или подходом в программировании. Этот подход обычно основан на условных выражениях, также известных как if во многих языках программирования.
Обработка ошибок и исключений после их возникновения появилась на сцене, когда языки программирования начали предоставлять механизмы обработки исключений, такие как try ... catch операторы в Java и C++, и try ... except операторы в Python. Однако в Java и C++ обработка исключений может быть дорогостоящей операцией, поэтому эти языки, как правило, предотвращают ошибки, а не обрабатывают их.
Примечание: Одна оптимизация, появляющаяся в Python 3.11 - это исключения с нулевыми затратами. Это означает, что стоимость try операторов будет практически сведена к минимуму, если не будет создано исключение.
В других языках программирования, таких как C и Go, даже нет механизмов обработки исключений. Так, например, программисты Go привыкли предотвращать ошибки с помощью условных операторов, как в следующем примере:
func SomeFunc(arg int) error { result, err := DoSomething(arg) if err != nil { // Handle the error here... log.Print(err) return err } return nil }предварительно> кодовый блок>Эта гипотетическая функция Go вызывает
DoSomething()и сохраняет свои возвращаемые значения вresultиerr. Переменнаяerrбудет содержать любую ошибку, возникающую во время выполнения функции. Если ошибка не возникает, тоerrбудет содержатьnil, которое является нулевым значением в Go.Затем оператор
ifпроверяет, отличается ли ошибка отnil, и в этом случае функция переходит к обработке ошибки. Этот шаблон довольно распространен, и вы неоднократно увидите его в большинстве программ Go.Механизмы обработки исключений в Python довольно эффективны, когда исключений не возникает. Поэтому в Python принято, а иногда и рекомендуется, устранять ошибки и исключительные ситуации, используя синтаксис языка для обработки исключений. Эта практика часто удивляет людей, которые знакомы с другими языками программирования.
Для вас это означает, что Python достаточно гибок и эффективен, чтобы вы могли выбрать правильную стратегию для устранения ошибок и исключительных ситуаций в вашем коде. Вы можете либо предотвратить ошибки с помощью условных операторов, либо обработать ошибки с помощью операторов
try...except.Специалисты по питону обычно используют следующую терминологию для обозначения этих двух стратегий борьбы с ошибками и исключительными ситуациями:
| Strategy | Terminology |
|---|---|
| Preventing errors from occurring | Look before you leap (LBYL) |
| Handling errors after they occur | Easier to ask forgiveness than permission (EAFP) |
В следующих разделах вы узнаете об этих двух стратегиях, также известных как стили кодирования в Python и других языках программирования.
Языки программирования с дорогостоящими механизмами обработки исключений, как правило, полагаются на проверку возможных ошибок до их возникновения. В этих языках обычно используется стиль LBYL. Python, напротив, с большей вероятностью будет полагаться на свой механизм обработки исключений при работе с ошибками и исключительными ситуациями.
Ознакомившись с этим кратким введением о стратегиях работы с ошибками и исключениями, вы будете готовы глубже погрузиться в стили программирования Python LBYL и EAFP и изучить, как использовать их в своем коде.
Стиль “Посмотри, прежде чем прыгнуть” (LBYL)
LBYL, или "посмотри, прежде чем прыгать", - это когда ты сначала проверяешь, получится ли что-то, а затем приступаешь к работе, только если знаешь, что это сработает. Документация по Python определяет этот стиль программирования следующим образом:
Смотрите, прежде чем прыгать. Этот стиль кодирования явно проверяет наличие предварительных условий перед выполнением вызовов или поисков. Этот стиль контрастирует с подходом EAFP и характеризуется наличием множества
ifутверждений. (Источник)
Чтобы понять суть LBYL, вы воспользуетесь классическим примером обработки пропущенных ключей в словаре.
Допустим, у вас есть словарь, содержащий некоторые данные, и вы хотите обработать ключ за ключом. Вы заранее знаете, что словарь может содержать некоторые специфические ключи. Вы также знаете, что некоторые ключи могут отсутствовать. Как вы можете справиться с отсутствующими ключами, не получив KeyError, который нарушает ваш код?
У вас есть несколько способов решить эту проблему. Сначала подумайте, как бы вы решили ее, используя условный оператор:
if "possible_key" in data_dict: value = data_dict["possible_key"] else: # Handle missing keys here...предварительно> кодовый блок>В этом примере вы сначала проверяете, присутствует ли
"possible_key"в целевом словаре,data_dict. Если это так, то вы получаете доступ к ключу и присваиваете его содержимому значениеvalue. Таким образом, вы предотвращаете исключениеKeyError, и ваш код не прерывается. Если"possible_key"отсутствует, то вы решаете проблему в предложенииelse.Этот способ решения проблемы известен как LBYL, поскольку он основан на проверке предварительного условия перед выполнением желаемого действия. LBYL - это традиционный стиль программирования, при котором вы убедитесь, что фрагмент кода будет работать, прежде чем запускать его. Если вы будете придерживаться этого стиля, то в конечном итоге получите множество
ifинструкций по всему вашему коду.Это не единственный и не самый распространенный способ решения проблемы с отсутствующим ключом в Python. Вы также можете использовать стиль кодирования EAFP, с которым вы ознакомитесь далее.
Стиль “Легче просить прощения, чем разрешения” (EAFP)
Грейс Мюррей Хоппер, американский ученый-компьютерщик-новатор, внесшая ряд выдающихся вкладов в компьютерное программирование, дала ценный совет и поделилась мудростью, сказав:
Легче попросить прощения, чем получить разрешение. (Источник)
EAFP, или проще просить прощения, чем разрешения, является конкретным выражением этого совета применительно к программированию. Он предполагает, что вам следует сразу же делать то, что, как вы ожидаете, сработает. Если это не сработает и произойдет исключение, то просто перехватите исключение и обработайте его соответствующим образом.
Согласно официальному словарю терминов Python, стиль кодирования EAFP имеет следующее определение:
Легче попросить прощения, чем разрешения. Этот распространенный стиль программирования на Python предполагает наличие допустимых ключей или атрибутов и перехватывает исключения, если предположение оказывается ложным. Этот чистый и быстрый стиль характеризуется наличием множества
tryиexceptутверждений. Этот метод отличается от стиля LBYL, общего для многих других языков, таких как C. (Источник)В Python стиль кодирования EAFP довольно популярен и распространен повсеместно. Иногда его рекомендуют использовать вместо стиля LBYL.
У этой популярности есть, по крайней мере, два мотивирующих фактора:
- Обработка исключений в Python выполняется быстро и эффективно.
- Необходимые проверки на наличие потенциальных проблем обычно являются частью самого языка.
Как говорится в официальном определении, стиль программирования EAFP характеризуется использованием
try...exceptинструкций для обнаружения и обработки ошибок и исключительных ситуаций, которые могут возникнуть во время выполнения вашего кода.Вот как переписать пример об обработке отсутствующих ключей из предыдущего раздела, используя стиль EAFP:
try: value = data_dict["possible_key"] except KeyError: # Handle missing keys here...предварительно> кодовый блок>В этом варианте вы не проверяете наличие ключа перед его использованием. Вместо этого вы продолжаете и пытаетесь получить доступ к нужному ключу. Если по какой-то причине ключ отсутствует, то вы просто перехватываете
KeyErrorв предложенииexceptи обрабатываете его соответствующим образом.Этот стиль отличается от стиля LBYL. Вместо того, чтобы постоянно проверять предварительные условия, он сразу же выполняет требуемое действие и ожидает, что операция завершится успешно.
Как это сделать на языке Python: LBYL или EAFP?
Какой язык Python лучше подходит для EAFP или для LBYL? Какой из этих стилей более похож на Python? Что ж, похоже, что разработчики Python в целом предпочитают EAFP, а не LBYL. Такое поведение основано на нескольких причинах, которые вы вскоре изучите.
Однако факт остается фактом: Python как язык не имеет явных предпочтений в отношении этих двух стилей кодирования. Гвидо ван Россум, создатель Python, сказал следующее:
[…] Я не согласен с позицией, что EAFP лучше, чем LBYL, или “обычно рекомендуется” Python. (Источник)
Как и во многих других вещах в жизни, ответ на первоначальные вопросы таков: это зависит от обстоятельств! Если текущая проблема подсказывает, что EAFP - лучший подход, тогда действуйте. С другой стороны, если лучшее решение подразумевает использование LBYL, то используйте его, не думая, что вы нарушаете правило Python.
Другими словами, вы должны быть готовы использовать в своем коде либо LBYL, либо EAFP. Любой из этих стилей может быть правильным решением в зависимости от вашей конкретной проблемы.
То, что может помочь вам решить, какой стиль использовать, - это ответить на вопрос: что удобнее в данной ситуации: предотвращать возникновение ошибок или обрабатывать их после они случаются? Подумайте над ответом и сделайте свой выбор. В следующем разделе вы рассмотрите плюсы и минусы LBYL и EAFP, которые могут помочь вам принять это решение.
Стили кодирования LBYL и EAFP в Python
Чтобы еще глубже понять, когда следует использовать стиль программирования Python LBYL или EAFP, вы сравните оба стиля, используя несколько соответствующих критериев сравнения:
- Количество проверок
- Удобочитаемость и ясность
- Риск для условий гонки
- Производительность кода
В следующих подразделах вы будете использовать приведенные выше критерии, чтобы узнать, как стили кодирования LBYL и EAFP могут повлиять на ваш код и какой стиль будет подходящим в соответствии с вашим конкретным вариантом использования.
Чтобы избежать ненужного повторения проверки
Одно из преимуществ EAFP перед LBYL заключается в том, что первый обычно помогает избежать ненужного повторения проверок. Например, предположим, что вам нужна функция, которая принимает положительные числа в виде строк и преобразует их к целочисленным значениям. Вы можете написать эту функцию с помощью LBYL, как в примере ниже:
>>> def to_integer(value): ... if value.isdigit(): ... return int(value) ... return None ... >>> to_integer("42") 42 >>> to_integer("one") is None Trueпредварительно> кодовый блок>В этой функции вы сначала проверяете, содержит ли
valueчто-то, что вы можете преобразовать в число. Чтобы выполнить проверку, вы используете метод.isdigit()из встроенного класса str. Этот метод возвращаетTrue, если все символы во входной строке являются цифрами. В противном случае он возвращаетFalse. Круто! Эта функциональность кажется правильным решением.Если вы опробуете эту функцию, то придете к выводу, что она работает так, как вы планировали. Она возвращает целое число, если входные данные содержат цифры и
Noneесли входные данные содержат хотя бы один символ, который не является цифрой. Однако в этой функции есть некоторое скрытое повторение. Вы можете это определить? Вызов функцииint()внутренне выполняет все необходимые проверки для преобразования входной строки в реальное целое число.Поскольку проверки уже являются частью
int(), проверка входной строки с помощью.isdigit()дублирует уже существующие проверки. Чтобы избежать этого ненужного повторения и соответствующих накладных расходов, вы можете использовать стиль EAFP и сделать что-то вроде этого:>>> def to_integer(value): ... try: ... return int(value) ... except ValueError: ... return None ... >>> to_integer("42") 42 >>> to_integer("one") is None Trueпредварительно> кодовый блок>Эта реализация полностью устраняет скрытые повторы, которые вы видели ранее. Она также обладает другими преимуществами, которые вы рассмотрите позже в этом руководстве, такими как улучшение читаемости и производительности.
Улучшение удобочитаемости и ясности
Чтобы узнать, как использование LBYL или EAFP влияет на читаемость и ясность вашего кода, предположим, что вам нужна функция, которая разделяет два числа. Функция должна быть способна определить, равен ли ее второй аргумент, знаменатель , ,
0, чтобы избежать исключенияZeroDivisionError. Если знаменатель равен0, то функция вернет значение по умолчанию, которое может быть указано в вызове в качестве необязательного аргумента .Вот реализация этой функции с использованием стиля кодирования LBYL:
>>> def divide(a, b, default=None): ... if b == 0: # Exceptional situation ... print("zero division detected") # Error handling ... return default ... return a / b # Most common situation ... >>> divide(8, 2) 4.0 >>> divide(8, 0) zero division detected >>> divide(8, 0, default=0) zero division detected 0предварительно> кодовый блок>Функция
divide()использует операторif, чтобы проверить, равен ли знаменатель при делении0. Если это так, то функция выводит сообщение на экран и возвращает значение, сохраненное вdefault, при этом изначально задано значениеNone. В противном случае функция разделяет оба числа и возвращает результат.Проблема с приведенной выше реализацией
divide()заключается в том, что она ставит исключительную ситуацию во главу угла, что влияет на читаемость кода и делает функцию неясной и трудной для понимания.В конечном счете, эта функция предназначена для вычисления деления двух чисел, а не для того, чтобы убедиться, что знаменатель не равен
0. Таким образом, в данном случае стиль LBYL может отвлекать внимание, привлекая внимание разработчика к исключительной ситуации, а не к обычному случаю.Теперь рассмотрим, как выглядела бы эта функция, если бы вы написали ее, используя стиль кодирования EAFP:
>>> def divide(a, b, default=None): ... try: ... return a / b # Most common situation ... except ZeroDivisionError: # Exceptional situation ... print("zero division detected") # Error handling ... return default ... >>> divide(8, 2) 4.0 >>> divide(8, 0) zero division detected >>> divide(8, 0, default=0) zero division detected 0предварительно> кодовый блок>в этой новой реализации
divide(), функции основной вычисление спереди и в центреtryпредложения, а исключительная ситуация перехвачено и обработано вexceptстатья в фоновом режиме.Когда вы начнете читать эту реализацию, вы сразу заметите, что функция предназначена для вычисления деления двух чисел. Вы также поймете, что в исключительных случаях второй аргумент может быть равен
0, генерируя исключениеZeroDivisionError, которое корректно обрабатывается в блоке кодаexcept.Как избежать условий гонки
Состояние гонки возникает, когда разные программы, процессы или потоки обращаются к данному вычислительному ресурсу одновременно. В этом случае программы, процессы или потоки соревнуются за доступ к нужному ресурсу.
Другая ситуация, в которой возникают условия гонки, - это когда заданный набор инструкций обрабатывается в неправильном порядке. Условия гонки могут вызывать непредсказуемые проблемы в базовой системе. Обычно их трудно обнаружить и отладить.
На странице глоссария Python прямо упоминается, что стиль кодирования LBYL создает риск возникновения условий гонки:
В многопоточной среде подход, основанный на LBYL, может привести к возникновению конкуренции между “смотрящим” и “прыгающим”. Например, код
if key in mapping: return mapping[key]может завершиться ошибкой, если другой поток удалитkeyизmappingпосле проверки, но перед поиском. Эта проблема может быть решена с помощью блокировок или с помощью подхода EAFP. (Источник)Риск возникновения условий гонки применим не только к многопоточным средам, но и к другим распространенным ситуациям в программировании на Python.
Например, предположим, что вы установили соединение с базой данных, с которой работаете. Теперь, чтобы предотвратить проблемы, которые могут привести к повреждению базы данных, вам нужно проверить, активно ли соединение:
connection = create_connection(db, host, user, password) # Later in your code... if connection.is_active(): # Update your database here... connection.commit() else: # Handle the connection error here...предварительно> кодовый блок>Если узел базы данных становится недоступным между вызовом
.is_active()и выполнением блока кодаif, то ваш код завершится ошибкой, поскольку узел недоступен.Чтобы предотвратить риск подобного сбоя, вы можете использовать стиль кодирования EAFP и сделать что-то вроде этого:
connection = create_connection(db, host, user, password) # Later in your code... try: # Update your database here... connection.commit() except ConnectionError: # Handle the connection error here...предварительно> кодовый блок>Этот код продолжает работу и пытается обновить базу данных, не проверяя, активно ли соединение, что устраняет риск возникновения ситуации гонки между проверкой и фактической операцией. Если возникает ошибка
ConnectionError, то блок кодаexceptобрабатывает ошибку соответствующим образом. Такой подход приводит к созданию более надежного кода, избавляя вас от сложных для отладки условий гонки.Повышение производительности вашего кода
Производительность является важной проблемой, когда речь заходит об использовании LBYL или EAFP. Если вы работаете на языке программирования с дорогостоящим процессом обработки исключений, то эта проблема вполне понятна.
Однако большинство реализаций Python приложили немало усилий, чтобы сделать обработку исключений дешевой операцией. Таким образом, при написании кода на Python вам не следует беспокоиться о стоимости исключений. Во многих случаях исключения могут работать быстрее, чем условные операторы.
Как показывает практика, если ваш код имеет дело со многими ошибками и исключительными ситуациями, то LBYL может быть более производительным, поскольку проверка многих условий обходится дешевле, чем обработка многих исключений.
Напротив, если ваш код сталкивается всего с несколькими ошибками, то EAFP, вероятно, является наиболее эффективной стратегией. В этих случаях EAFP будет быстрее, чем LBYL, потому что вам не придется обрабатывать много исключений. Вы просто будете выполнять требуемую операцию без дополнительных затрат на постоянную проверку предварительных условий.
В качестве примера того, как использование LBYL или EAFP может повлиять на производительность вашего кода, скажем, что вам нужно создать функцию, которая измеряет частоту символов в заданном тексте. Итак, в итоге вы пишете следующее:
>>> def char_frequency_lbyl(text): ... counter = {} ... for char in text: ... if char in counter: ... counter[char] += 1 ... else: ... counter[char] = 1 ... return counter ...предварительно> кодовый блок>Эта функция принимает фрагмент текста в качестве аргумента и возвращает словарь с символами в качестве ключей. Каждое соответствующее значение представляет количество раз, когда этот символ встречается в тексте.
Примечание: В разделе Повышение производительности вашего кода этого руководства вы найдете пример, который дополняет приведенный в этом разделе .
Чтобы создать этот словарь,
forцикл выполняет итерацию по каждому символу во входном тексте. На каждой итерации условный оператор проверяет, есть ли текущий символ уже в словареcounter. Если это так, то блок кодаifувеличивает количество символов на1.С другой стороны, если символа еще нет в
counter, то блок кодаelseдобавляет символ в качестве ключа и устанавливает его количество или частоту на начальное значение1. Наконец, функция возвращаетcounterсловарь.Если вы вызовете свою функцию с некоторым образцом текста, то получите результат, показанный ниже:
>>> sample_text = """ ... Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime ... mollitia, molestiae quas vel sint commodi repudiandae consequuntur ... voluptatum laborum numquam blanditiis harum quisquam eius sed odit ... fugiat iusto fuga praesentium optio, eaque rerum! Provident similique ... accusantium nemo autem. Veritatis obcaecati tenetur iure eius earum ... ut molestias architecto voluptate aliquam nihil, eveniet aliquid ... culpa officia aut! Impedit sit sunt quaerat, odit, tenetur error, ... harum nesciunt ipsum debitis quas aliquid. ... """ >>> char_frequency_lbyl(sample_text) {'\n': 9, 'L': 1, 'o': 24, 'r': 22, ..., 'V': 1, 'I': 1}предварительно> кодовый блок>Вызов
char_frequency_lbyl()сsample_textв качестве аргумента возвращает словарь, содержащий пары с количеством символов.Если вы немного поразмыслите над проблемой определения частоты встречаемости символов в тексте, то поймете, что вам нужно учитывать конечное количество символов. Теперь подумайте, как этот факт может повлиять на ваше решение. Наличие конечного числа символов означает, что вам придется выполнять множество ненужных проверок, чтобы убедиться, что текущий символ уже есть в счетчике.
Примечание: В Python есть специализированный класс
Counterв модулеcollections, который был разработан для решения проблемы подсчета объектов. Ознакомьтесь с Python's Counter: Pythonic Way для подсчета объектов для получения более подробной информации.После того, как функция обработала некоторый текст, вполне вероятно, что целевой символ уже находится в
counter, когда вы выполняете проверку. В конечном счете, все эти ненужные проверки приведут к снижению производительности вашего кода. Этот факт особенно актуален, если вы работаете с большими фрагментами текста.Как вы можете избежать этих дополнительных накладных расходов в вашем коде? Вот тут-то и пригодится EAFP. Вернитесь к своей интерактивной сессии и напишите следующую функцию:
>>> def char_frequency_eafp(text): ... counter = {} ... for char in text: ... try: ... counter[char] += 1 ... except KeyError: ... counter[char] = 1 ... return counter ... >>> char_frequency_eafp(sample_text) {'\n': 9, 'L': 1, 'o': 24, 'r': 22, ..., 'V': 1, 'I': 1}предварительно> кодовый блок>Эта функция выполняет те же действия, что и
char_frequency_lbyl()в предыдущем примере. Однако на этот раз функция использует стиль кодирования EAFP.Теперь вы можете выполнить быстрый
timeitпротестируйте производительность обеих функций, чтобы понять, какая из них быстрее:>>> import timeit >>> sample_text *= 100 >>> eafp_time = min( ... timeit.repeat( ... stmt="char_frequency_eafp(sample_text)", ... number=1000, ... repeat=5, ... globals=globals(), ... ) ... ) >>> lbyl_time = min( ... timeit.repeat( ... stmt="char_frequency_lbyl(sample_text)", ... number=1000, ... repeat=5, ... globals=globals(), ... ) ... ) >>> print(f"LBYL is {lbyl_time / eafp_time:.3f} times slower than EAFP") LBYL is 1.211 times slower than EAFPпредварительно> кодовый блок>В этом примере разница в производительности между функциями незначительна. Вероятно, вы можете сказать, что обе функции работают одинаково. Однако с увеличением размера текста разница в производительности между функциями пропорционально возрастает, и реализация EAFP в конечном итоге оказывается немного более эффективной, чем реализация LBYL.
Вывод из этого теста производительности заключается в том, что вам необходимо заранее определить, с какой проблемой вы имеете дело.
Верны ли ваши входные данные в основном? Вы сталкиваетесь только с несколькими ошибками? Требуют ли ваши предварительные условия больших затрат времени? Если ваш ответ на эти вопросы да, то склоняйтесь к EAFP. И наоборот, если ваши данные в плохом состоянии, вы ожидаете, что произойдет много ошибок, а ваши предварительные условия невелики, тогда выбирайте LBYL.
Подведение итогов: LBYL против EAFP
Ого! Вы многое узнали о стилях программирования Python LBYL и EAFP. Теперь вы знаете, что это за стили и каковы их преимущества. Чтобы ознакомиться с основными темами и выводами из этого раздела, ознакомьтесь со следующей таблицей:
Criteria LBYL EAFP Number of checks Repeats checks typically provided by Python Runs the checks provided by Python only once Readability and clarity Has poor readability and clarity because exceptional situations seem to be more important than the target operation itself Has enhanced readability because the target operation is front and center, while the exceptional situations are relegated to the background Race condition risk Implies a risk of race conditions between the check and the target operation Prevents the risk of race conditions because the operation runs without doing any checks Code performance Has poor performance when the checks almost always succeed, and better performance when the checks almost always fail Has better performance when the checks almost always succeed, and poor performance when the checks almost always fail Теперь, когда вы углубились в сравнение LBYL и EAFP, пришло время узнать о нескольких распространенных недостатках обоих стилей кодирования и о том, как избежать их в вашем коде. Наряду с темами, кратко изложенными в таблице выше, эти нюансы могут помочь вам решить, какой стиль использовать в той или иной ситуации.
Общие недостатки LBYL и EAFP
Когда вы пишете свой код, используя стиль LBYL, вы должны осознавать, что можете опустить определенные условия, которые необходимо проверить. Чтобы прояснить этот момент, вернемся к примеру, в котором вы преобразовали строковые значения в целые числа:
>>> value = "42" >>> if value.isdigit(): ... number = int(value) ... else: ... number = 0 ... >>> number 42предварительно> кодовый блок>По-видимому, проверка
.isdigit()удовлетворяет всем вашим требованиям. Однако, что делать, если вам нужно обработать строку, представляющую отрицательное число? Подойдет ли вам.isdigit()? Запустите приведенный выше пример с допустимым отрицательным числом в виде строки и проверьте, что произойдет:>>> value = "-42" >>> if value.isdigit(): ... number = int(value) ... else: ... number = 0 ... >>> number 0предварительно> кодовый блок>Теперь вы получаете
0вместо ожидаемого числа-42. Что только что произошло? Что ж,.isdigit()проверяет только цифры от0до9. Отрицательные числа не проверяются. Такое поведение делает вашу проверку неполной в соответствии с вашими новыми требованиями. Вы опускаете отрицательные числа при проверке предварительных условий.Вы также можете подумать об использовании
.isnumeric(), но этот метод также не возвращаетTrueс отрицательными значениями:>>> value = "-42" >>> if value.isnumeric(): ... number = int(value) ... else: ... number = 0 ... >>> number 0предварительно> кодовый блок>Эта проверка не соответствует вашим требованиям. Вам нужно попробовать что-то другое. Теперь подумайте, как вы можете предотвратить риск пропуска необходимых проверок в этом примере. Да, вы можете использовать стиль кодирования EAFP:
>>> value = "-42" >>> try: ... number = int(value) ... except ValueError: ... number = 0 ... >>> number -42предварительно> кодовый блок>Круто! Теперь ваш код работает должным образом. Он преобразует положительные и отрицательные значения. Почему? Поскольку все условия, необходимые для преобразования строки в целое число, по умолчанию неявно включены в вызов функции
int().На данный момент кажется, что EAFP - это решение всех ваших проблем. Однако это не всегда так. У этого стиля также есть свои недостатки. В частности, вы не должны запускать код с побочными эффектами.
Рассмотрим следующий пример, в котором приветствие записывается в текстовый файл:
moments = ["morning", "afternoon", "evening"] index = 3 with open("hello.txt", mode="w", encoding="utf-8") as hello: try: hello.write("Good\n") hello.write(f"{moments[index]}!") except IndexError: passпредварительно> кодовый блок>В этом примере у вас есть список строк, представляющих различные моменты в течение дня. Затем вы используете инструкцию
with, чтобы открыть файлhello.txtв режиме записи,"w".Блок кода
tryвключает в себя два вызова.write(). Первый из них записывает начальную часть приветствия в целевой файл. Второй вызов завершает приветствие, извлекая момент из списка и записывая его в файл.Оператор
exceptперехватывает любойIndexErrorво время второго вызова.write(), который выполняет операцию индексации для получения соответствующего аргумента. Если индекс находится вне диапазона, как в примере, то вы получаете значениеIndexError, а блокexceptотключает ошибку. Однако при первом вызове.write()уже была выполнена запись"Good\n"в файлhello.txt, который в конечном итоге переходит в нежелательное состояние.В некоторых ситуациях этот побочный эффект может быть трудно устранить, поэтому вам лучше избегать подобных действий. Чтобы устранить эту проблему, вы можете сделать что-то вроде этого:
moments = ["morning", "afternoon", "evening"] index = 3 with open("hello.txt", mode="w", encoding="utf-8") as hello: try: moment = f"{moments[index]}!" except IndexError: pass else: hello.write("Good\n") hello.write(moment)предварительно> кодовый блок>На этот раз блок кода
tryвыполняет только индексацию, которая является операцией, которая может вызватьIndexError. Если возникает такая ошибка, то вы просто игнорируете ее в блоке кодаexcept, оставляя файл пустым. Если индексация прошла успешно, то вы записываете полное приветствие в файл в блоке кодаelse.Попробуйте оба фрагмента кода с
index = 0иindex = 3, чтобы проверить, что происходит с вашим файломhello.txt.Вторая проблема EAFP возникает, когда вы используете расширенный класс исключений в своем
exceptзаявлении. Например, если вы работаете с фрагментом кода, который может вызывать несколько типов исключений, то вы можете подумать об использовании классаExceptionв инструкцииexceptили, что еще хуже, вообще не использовать класс исключений.Почему такая практика является проблемой? Ну, класс
Exceptionявляется родительским классом почти для всех встроенных исключений Python. Таким образом, вы будете перехватывать практически все, что есть в вашем коде. Отсюда следует вывод, что у вас не будет четкого представления о том, с какой ошибкой или исключением вы имеете дело в данный момент.На практике избегайте выполнения чего-либо подобного:
try: do_something() except Exception: passпредварительно> кодовый блок>Очевидно, что ваша функция
do_something()может вызывать множество типов исключений. Во всех случаях вы просто отключаете ошибку и продолжаете выполнение вашей программы. Замалчивание всех ошибок, включая неизвестные, может привести к неожиданным ошибкам позже, и это нарушает фундаментальный принцип Дзен Python: Ошибки никогда не должны передаваться без предупреждения.Чтобы уберечь себя от головной боли в будущем, старайтесь как можно чаще использовать конкретные исключения. Используйте те исключения, которые вы сознательно ожидаете от своего кода. Помните, что у вас может быть несколько
exceptветвей. Например, предположим, что вы протестировали функциюdo_something()и ожидаете, что она вызоветValueErrorиIndexErrorисключения. В этом случае вы можете сделать что-то вроде этого:try: do_something() except ValueError: # Handle the ValueError here... except IndexError: # Handle the IndexError here...предварительно> кодовый блок>В этом примере наличие нескольких ветвей
exceptпозволяет соответствующим образом обрабатывать каждое ожидаемое исключение. Преимущество этой конструкции также в том, что она упрощает отладку вашего кода. Почему? Потому что ваш код немедленно завершится ошибкой, еслиdo_something()возникнет непредвиденное исключение. Таким образом, вы предотвратите передачу неизвестных ошибок без предупреждения.EAFP против LBYL С примерами
На данный момент вы узнали, что такое LBYL и EAFP, как они работают и каковы плюсы и минусы обоих стилей кодирования. В этом разделе вы узнаете немного больше о том, когда следует использовать тот или иной стиль. Чтобы сделать это, вы напишете несколько практических примеров.
Прежде чем приступить к примерам, вот краткое описание того, когда следует использовать LBYL или EAFP:
Use LBYL for Use EAFP for Operations that are likely to fail Operations that are unlikely to fail Irrevocable operations, and operations that may have a side effect Input and output (IO) operations, mainly hard drive and network operations Common exceptional conditions that can be quickly prevented beforehand Database operations that can be rolled back quickly Ознакомившись с этим кратким изложением, вы готовы приступить к использованию LBYL и EAFP для написания нескольких практических примеров, которые продемонстрируют плюсы и минусы обоих стилей программирования в реальной жизни.
Работа со слишком большим количеством ошибок или исключительных ситуаций
Если вы ожидаете, что ваш код столкнется с массой ошибок и исключительных ситуаций, рассмотрите возможность использования LBYL поверх EAFP. В таких ситуациях LBYL будет более безопасным и, вероятно, более производительным.
Например, предположим, что вы хотите запрограммировать функцию, которая вычисляет частоту встречаемости слов в тексте. Для этого вы планируете использовать словарь. Ключи будут содержать слова, а значения - их количество или частоту.
Поскольку в естественных языках слишком много возможных слов, ваш код будет иметь дело со многими
KeyErrorисключениями. Несмотря на это, вы решили использовать стиль кодирования EAFP. В итоге вы получаете следующую функцию:>>> def word_frequency_eafp(text): ... counter = {} ... for word in text.split(): ... try: ... counter[word] += 1 ... except KeyError: ... counter[word] = 1 ... return counter ... >>> sample_text = """ ... Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime ... mollitia, molestiae quas vel sint commodi repudiandae consequuntur ... voluptatum laborum numquam blanditiis harum quisquam eius sed odit ... fugiat iusto fuga praesentium optio, eaque rerum! Provident similique ... accusantium nemo autem. Veritatis obcaecati tenetur iure eius earum ... ut molestias architecto voluptate aliquam nihil, eveniet aliquid ... culpa officia aut! Impedit sit sunt quaerat, odit, tenetur error, ... harum nesciunt ipsum debitis quas aliquid. ... """ >>> word_frequency_eafp(sample_text) {'Lorem': 1, 'ipsum': 2, 'dolor': 1, ..., 'aliquid.': 1}предварительно> кодовый блок>Эта функция создает словарь
counterдля хранения слов и их количества. Циклforвыполняет итерацию по словам во входном тексте. Внутри блокаtryвы пытаетесь обновить количество текущего слова, добавив1к его предыдущему значению. Если целевое слово не существует в качестве ключа вcounter, то эта операция вызываетKeyError.Оператор
exceptперехватывает исключениеKeyErrorи инициализирует отсутствующий ключ — слово — вcounterзначением1.Когда вы вызываете свою функцию с некоторым образцом текста, вы получаете словарь со словами в качестве ключей и числами в качестве значений. Вот и все! Вы решили проблему!
Ваша функция выглядит неплохо! Вы используете стиль EAFP, и это работает. Однако эта функция может работать медленнее, чем ее аналог в LBYL:
>>> def word_frequency_lbyl(text): ... counter = {} ... for word in text.split(): ... if word in counter: ... counter[word] += 1 ... else: ... counter[word] = 1 ... return counter ... >>> word_frequency_lbyl(sample_text) {'Lorem': 1, 'ipsum': 2, 'dolor': 1, ..., 'aliquid.': 1}предварительно> кодовый блок>В этом варианте вы используете условный оператор, чтобы заранее проверить, существует ли текущее слово в словаре
counter. Если это так, то вы увеличиваете количество на1. В противном случае вы создаете соответствующий ключ и инициализируете его значение как1. Когда вы запускаете функцию с образцом текста, вы получаете тот же словарь пар для подсчета слов.Эта реализация на основе LBYL дает тот же результат, что и реализация на основе EAFP. Однако ее производительность может быть выше. Чтобы подтвердить эту возможность, выполните следующие тесты производительности:
>>> import timeit >>> lbyl_time = min( ... timeit.repeat( ... stmt="word_frequency_lbyl(sample_text)", ... number=1000, ... repeat=5, ... globals=globals(), ... ) ... ) >>> eafp_time = min( ... timeit.repeat( ... stmt="word_frequency_eafp(sample_text)", ... number=1000, ... repeat=5, ... globals=globals(), ... ) ... ) >>> print(f"EAFP is {eafp_time / lbyl_time:.3f} times slower than LBYL") EAFP is 2.117 times slower than LBYLпредварительно> кодовый блок>EAFP не всегда является лучшим решением всех ваших проблем. В этом примере EAFP работает более чем в два раза медленнее, чем LBYL.
Итак, если в вашем коде часто встречаются ошибки и исключительные ситуации, отдавайте предпочтение LBYL, а не EAFP. Многие условные операторы могут работать быстрее, чем многие исключения, поскольку проверка условия по-прежнему обходится дешевле, чем обработка исключения в Python.
Проверка типа и атрибутов объектов
Проверка типа объекта в Python широко рассматривается как антипаттерн, и его следует избегать, насколько это возможно. Некоторые разработчики ядра Python явно назвали эту практику антипаттерном, сказав следующее:
[...] в настоящее время это распространенный анти-шаблон для кода на Python, позволяющий проверять типы полученных аргументов, чтобы решить, что делать с объектами.
[Этот шаблон кодирования является] “хрупким и закрытым для расширения” (Источник).
Использование анти-шаблона проверки типов повлияет, по крайней мере, на два основных принципа программирования на Python:
- Полиморфизм, при котором один интерфейс может обрабатывать объекты разных классов
- Уточняющий ввод, когда объект обладает характеристиками, определяющими, можно ли его использовать для данной цели
Python обычно полагается на поведение объекта, а не на его тип. Например, у вас должна быть функция, которая ожидает аргумент с помощью метода
.append(). Напротив, у вас не должно быть функции, которая ожидает аргументlist. Почему? Потому что привязка поведения функции к типу аргумента приводит к потере точности ввода.Рассмотрим следующую функцию:
def add_users(username, users): if isinstance(users, list): users.append(username)предварительно> кодовый блок>Эта функция работает нормально. Она берет имя пользователя и список пользователей и добавляет новое имя пользователя в конец списка. Однако эта функция не использует преимущества утиной типизации, поскольку она полагается на тип своего аргумента, а не на требуемое поведение, которое имеет метод
.append().Например, если вы решите использовать объект
collections.deque()для хранения вашего спискаusers, вам придется изменить эту функцию, если вы хотите, чтобы ваш код продолжал работать.Чтобы не жертвовать утиным вводом при проверке типов, вы можете использовать стиль кодирования EAFP:
def add_user(username, users): try: users.append(username) except AttributeError: passпредварительно> кодовый блок>Эта реализация
add_user()зависит не от типаusers, а от его.append()поведения. С этой новой реализацией вы можете сразу начать использовать объектdequeдля хранения списка пользователей или продолжить использовать объектlist. Вам не нужно будет изменять функцию, чтобы ваш код продолжал работать.Обычно Python взаимодействует с объектами, напрямую вызывая их методы и получая доступ к их атрибутам, не проверяя предварительно тип объекта. В таких случаях лучше всего использовать стиль кодирования EAFP.
Практика, которая также влияет на полиморфизм и упрощенную типизацию, заключается в том, что вы проверяете, имеет ли объект определенные атрибуты, прежде чем обращаться к нему в своем коде. Рассмотрим следующий пример:
def get_user_roles(user): if hasattr(user, "roles"): return user.roles return Noneпредварительно> кодовый блок>В этом примере
get_user_roles()использует стиль кодирования LBYL, чтобы проверить, имеет ли объектuserатрибут.roles. Если это так, то функция возвращает содержимое.roles. В противном случае функция возвращаетNone.Вместо того, чтобы проверять, имеет ли
userатрибут.rolesс помощью встроенной функцииhasattr(), вам следует просто продолжить и получить доступ к атрибуту напрямую с помощью стиля EAFP:def get_user_roles(user): try: return user.roles except AttributeError: return Noneпредварительно> кодовый блок>Этот вариант
get_user_roles()более явный, прямой и понятный. Он более понятен на языке Python, чем вариант, основанный на LBYL. Наконец, он также может быть более эффективным, поскольку не требует постоянной проверки предварительного условия с помощью вызоваhasattr().Работа с файлами и каталогами
Управление файлами и каталогами в вашей файловой системе иногда является обязательным требованием в ваших приложениях на Python и проектах. Когда дело доходит до обработки файлов и каталогов, многое может пойти не так.
Например, предположим, что вам нужно открыть определенный файл в вашей файловой системе. Если вы используете стиль кодирования LBYL, то в итоге у вас может получиться фрагмент кода, подобный этому:
from pathlib import Path file_path = Path("/path/to/file.txt") if file_path.exists(): with file_path.open() as file: print(file.read()) else: print("file not found")предварительно> кодовый блок>Если вы запустите этот код, нацеленный на файл в вашей файловой системе, содержимое файла будет выведено на экран. Итак, этот код работает. Однако в нем есть скрытая проблема. Если по какой-либо причине ваш файл будет удален в промежутке между моментом, когда вы проверяете, завершен ли файл, и моментом, когда вы пытаетесь его открыть, операция открытия файла завершится ошибкой, и ваш код завершится сбоем.
Как вы можете избежать такого рода проблем? Ну, вы можете использовать стиль кодирования EAFP, как в приведенном ниже коде:
from pathlib import Path file_path = Path("/path/to/file.txt") try: with file_path.open() as file: print(file.read()) except IOError as e: print("file not found")предварительно> кодовый блок>Вместо того, чтобы проверять, можете ли вы открыть файл, вы просто пытаетесь открыть его. Если это сработает, то отлично! Если это не сработает, то вы обнаружите ошибку и обработаете ее соответствующим образом. Обратите внимание, что вы больше не рискуете попасть в условия гонки. Теперь вы в безопасности.
Заключение
Теперь вы знаете, что в Python есть смотри, прежде чем прыгнуть (LBYL) и легче попросить прощения, чем разрешения (EAFP). стили, которые являются общими стратегиями для устранения ошибок и исключительных ситуаций в вашем коде. Вы также узнали, что это за стили кодирования и как их использовать в вашем коде.
В этом уроке вы узнали:
- Основы стилей программирования Python LBYL и EAFP
- Плюсы и минусы LBYL против EAFP в Python
- Ключевые моменты для принятия решения о том, когда использовать либо LBYL, либо EAFP
Благодаря этим знаниям о стилях программирования Python LBYL и EAFP, вы теперь сможете решить, какую стратегию использовать при работе с ошибками и исключительными ситуациями в вашем коде.
Бесплатный бонус: 5 Размышления о мастерстве владения Python, бесплатный курс для разработчиков Python, который показывает вам план действий и мышление, с которым вы будете работать. вам нужно поднять свои навыки работы с Python на новый уровень.
<статус завершения article-slug="python-lbyl-vs-eafp" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/python-lbyl-vs-eafp/закладка/" data-api-статья-статус завершения-url="/api/v1/articles/python-lbyl-vs-eafp/completion_status/"> статус завершения> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0ALBYL против EAFP: Предотвращение или обработка ошибок в Python" email-subject="Статья о Python для вас" twitter-text="Интересно #Статья на Python от @realpython:" url="https://realpython.com/python-lbyl-vs-eafp/" url-title="LBYL против EAFP: предотвращение или обработка ошибок в Python"> кнопка поделиться>Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Обработка или предотвращение ошибок в Python: LBYL против EAFP
Back to Top