Рубрики
Без рубрики

Полное руководство по обработке исключений в Python

Правила и правила наилучшей обработки исключений Python

Автор оригинала: Henry George.

Никто не хочет, чтобы их код выбрасывал ошибки, но исключения в Python могут иметь целый ряд вариантов использования и критически важны для написания хорошего кода. В этой статье описаны некоторые примеры того, что делать и чего не делать при написании исключений. Надеюсь, это привьет вам более глубокое понимание обработки исключений в целом, а также даст некоторые полезные советы по Python.

Пишите много исключений

  • Попытка Доступа К Основной Базе Данных До Исключения Инициированной Транзакции Базы Данных
  • Javascript Миллисекундный Формат времени BufferOverflowException
  • Невозможно Удалить Учетную Запись Пользователя, Которая Не Существует Исключения

Исключения, исключения, исключения повсюду. Эти имена выглядят сложными для чтения и длинными для ввода, но действительно ли они так страшны? Эти исключения обеспечивают наиболее ценную функцию исключительно хорошо — они конкретны, информативны и по существу. Хорошо, последний , вероятно, будет переименован в Не может удалить несуществующего пользователя , конечно, но суть в том, что они ясны. Вы знаете, куда обратиться, чтобы найти проблему, и вы точно знаете, что нужно попробовать поймать, если вы согласны с попыткой удалить пользователя, который уже был удален. Когда-то был ti me до Atom, Visual Studio и IntelliJ, в стране Vi и Nano, где длина имени переменной фактически влияла на производительность разработчиков. Я научился кодировать на C, используя Vim, без автозаполнения. И да, я называл вещи int num или |/char* s1 . Конечно, я был ленив. Конечно, я был молодым студентом. Но кроме того, мне пришлось ввести все три этих символа в num. В наши дни мне повезет напечатать больше двух раньше

Моя среда IDE автоматически заполняет длинные исключения всего после 1 символа

Запутанные ответы API переводятся в явное исключение

Некоторые провайдеры просто хотят посмотреть, как горит весь мир. Мы определенно могли бы реорганизовать это, чтобы сделать коды ответов перечислением или аналогичным выразительным форматом, но в этом коде нет никакой путаницы в том, что какими бы ни были эти случайные коды ответов, они означают, что у нас не включена 2-факторная аутентификация, и нам нужно настроить этот параметр в нашем примере поставщика, чтобы заставить это работать.

Эмпирическое правило: всякий раз, когда у вас есть информация о конкретном, воспроизводимом крайнем случае, используйте ее.

Но зачем вообще беспокоиться об исключениях? Мы постоянно используем утверждения в тестировании, и они отлично справляются с этой задачей. Почему бы просто не использовать их вместо исключений?

Не “утверждайте”, если вы не участвуете в тесте

assert и raise , по-видимому, работают аналогично. Оба останавливают поток управления, оба могут завершить программу, и оба могут войти/распечатать сообщение, объясняющее, почему.

Поначалу может показаться заманчивым использовать asserts для подтверждения того, что все находится в допустимом состоянии, но это считается плохой практикой в корпоративной разработке Python. Есть несколько причин, почему, и они могут быть самостоятельной статьей.

Но чтобы быть кратким: вы можете настроить обработку исключений и детали исключений, и исключение поднимает никогда не будет “оптимизировано” из вашего кода .

Эмпирическое правило: вы должны только утверждать невозможные условия, такие как утверждение, что значение, которое вы только что возводили в квадрат, не является отрицательным (предполагая, что вы не моделируете квантовую физику или что-то столь же дикое). Если есть хотя бы отдаленная вероятность того, что утверждение может потерпеть неудачу, то его следует заменить исключением.

Классический пример, когда люди часто совершают эту ошибку,-это когда имеют дело со сторонними поставщиками.

Допустим, вы вызываете API погоды Yahoo в своем приложении, и вы решили добавить утверждение , что ответ не является None . Вы выбрали assert , потому что Yahoo всегда возвращает погоду (хотя прогнозы не всегда могут быть правильными). Но что происходит, когда API погоды Yahoo испытывает сбой? Внезапно все ваши службы отказывают, и все, с чем вам нужно работать, – это AssertionError: response is None .

Теперь я уверен, что погода Yahoo, вероятно, очень надежна, и у вас, вероятно, есть номера строк в ваших журналах. Но это не причина, чтобы не потратить одну минуту дополнительного времени на разработку и не создать исключение WeatherProviderUnresponsiveException (“называть вещи сложно … [ дайте этому писателю перерыв ]” — Фил Карлтон).

Итак, мы должны использовать множество исключений, но какие исключения мы должны использовать? Прежде чем мы погрузимся в это, нам нужно понять важнейшую концепцию обработки исключений (и программирования в целом).

Быстрая касательная — Что такое наследование типов?

Давайте начнем с основ, потому что нужно многое распаковать.

Во-первых, наследование. Когда класс в Python наследуется от другого класса, он принимает все методы и атрибуты, но он также принимает тип родительского класса — то есть в мире Python я-это я, но я также мой отец и моя мать. Даже если вы переопределите методы или атрибуты родительских классов, вы все равно сохраните наследование типов. Давайте рассмотрим какой – нибудь код для объяснения.

Только одно замечание перед погружением: классы-это не совсем типы, но они вроде как . Хорошо, теперь, когда вы запутались больше, чем когда-либо, давайте посмотрим на некоторый код:

Сценарий для объяснения наследования типов

Здесь мы определили несколько пользовательских типов, все они наследуются от встроенного класса dict. Возьмем, например, Reddit , который имеет тип Reddit, но также и dict.

isinstance(red_map, dict) # True
isinstance(red_map, RedDict) # True

Однако обратное неверно — в том, что RedDict не является PurpleDict .

isinstance(red_map, PurpleDict) # False

Запустите приведенный выше фрагмент кода, чтобы увидеть результаты для всех вышеперечисленных классов.

Вы можете объективировать все, что хотите, в Python, так как все является объектом

Вы можете объективировать все, что хотите, в Python, так как все является объектом

Вы можете объективировать все, что хотите, в Python, так как все является объектом, теперь давайте вернемся к тому, где мы были …

try:
segway_about_types()
catch OnATangentError, Exception as e:
smooth_transition(e)

Не ловите “Исключение как e”

Перехват всех исключений и выбрасывание их-второй наиболее эффективный способ получить код без ошибок ( первый – удалить весь код ). Так что, конечно, это означает, что это хорошо, не так ли?

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

Проблема с перехватом Exception связана с наследованием типов (следовательно, интерлюдией), потому что мы будем перехвачивать не только все пользовательские исключения в вашем приложении, но и целую кучу встроенных исключений Python, включая некоторые, которые вы, возможно, не захотите выбрасывать.

Встроенные исключения Python имеют сложную структуру наследования. Вот последний список из документов, где каждый отступ означает наследование.

Разве вы не хотите, чтобы в документах Python3 были красивые диаграммы?

Официальные встроенные исключения python3

Эта иерархия создана по уважительной причине, поэтому вы можете использовать наследование типов, чтобы быть умным в том, как вы ловите исключения (см. Следующий пункт).

То, что мы только что узнали о наследовании типов, говорит нам, что это означает TypeError также является Исключением (его родителем), поэтому Ошибки типа будут пойманы, когда мы поймаем Исключение . Наверное, это нормально.

Но как насчет ModuleNotFoundError ? Вы действительно хотите, чтобы ваша программа продолжала работать, если в ней полностью отсутствует зависимость? А как насчет Ошибки памяти ? Конечно, вы не хотите поворачиваться спиной, когда Python задыхается на вашей карте памяти.

Вы будете ловить не только все эти дикие и замечательные встроенные исключения, но и все пользовательские исключения (да … за исключением тех, которые являются производными от BaseException , а не Exception ).

Это действительно то, что ты хотел сделать? Возможно, решение состоит в том, чтобы поймать несколько конкретных пользовательских исключений (которые вы делаете с помощью кортежей), например:

catch (FileNotFoundError, IsADirectoryError, PermissionError) as e:

Это безопасно поймает FileNotFoundError , но не поймает более опасную OSError , такую как Ошибка дочернего процесса .

Конечно, есть сценарии, в которых вы хотите поймать все исключения, но их немного и они далеко друг от друга. Также важно отметить, что здесь важно, как вы обрабатываете исключение. Если вы ловите все исключения , но затем снова вызываете это исключение или используете logger.exception () , это не проблема.

Некоторые примеры, когда вы можете захотеть перехватить все исключения: -При извлечении из очереди и обработке одного сообщения за раз вы можете использовать logger.exception() для регистрации проблемы, не нарушая поток -В рамках практики chaos-engineering на уровне обслуживания, особенно для асинхронных служб, вы можете перехватывать все исключения, безопасно закрывать все, а затем вызывать или регистрировать исключения -Веб — очистка или обход ссылок является грязной задачей и часто вызывает всевозможные ошибки-в некоторых случаях это требует очень широкой обработки исключений.

Эмпирическое правило: поймайте как можно больше ошибок. Не ловите исключение как e, если вы точно не знаете, что делаете.

Я уже упоминал, что встроенная иерархия исключений полезна. Давайте посмотрим, как исключения могут работать на нас, а не против нас.

Используйте Встроенные Исключения, Когда Это Разумно

Это может звучать как контраргумент к предыдущему решению о написании большого количества исключений, но иногда имеет смысл использовать простое встроенное исключение Python, такое как ValueError или TypeError .

Допустим, вы создаете автомобиль, который может быть электрическим, бензиновым или гибридным, и вы хотите передать два логических значения, чтобы создать автомобиль, который определяет тип двигателя.

Класс электромобилей python с двумя логическими переменными по умолчанию, вызывающими ошибку ValueError

Это идеальный пример, где подходит ValueError . Вы не можете определить правило, согласно которому хотя бы один из электрических или бензиновых должен быть true в определении функции (функции переопределения RIP), поэтому вам нужно будет проверить это вручную.

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

Это отличная возможность использовать встроенный ValueError , так как многие программы могут попытаться поймать эту ошибку и использовать ее. Например, программа для автоматического запуска параметрических тестов может проходить через каждую комбинацию true , false и catch и пропускать все , что возвращает ValueErrors .

Тем не менее, я хотел бы сделать еще один шаг вперед. Мы можем использовать наследование типов в нашу пользу, чтобы добавить удобочитаемость нашему коду, наследовав наши исключения от более конкретного исключения, такого как ValueError . Таким образом, любая программа, которая ловит ValueError , также поймает наше исключение, но мы можем добавить пользовательский код и имена.

Тот же пример, что и выше, но с определенным пользовательским исключением

Нужно … написать … больше … исключений.

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

Полный список встроенных исключений Python и их иерархию см. в официальных документах .

Не помещайте Конфиденциальные данные в Сообщения об исключениях

Мне нужна защищенная система, поэтому имеет смысл вызвать исключение общего пароля(f"пароль: {пароль} слишком распространен") . (Не уверен, что это означает f? Ознакомьтесь с моей статьей об этом ).

Это хорошее исключение — понятно, что означает исключение, я предоставил ему достаточную информацию, и оно достаточно специфично, его можно было бы безопасно завернуть в try-catch и обрабатывать по-другому, если бы, например, мы были более расслаблены с паролями администратора, чем с пользователями.

Проблема здесь сводится к конфиденциальным данным. Я привел здесь несправедливый пример, поэтому я надеюсь, что для вас совершенно очевидно, что это не хорошее исключение. Это исключение будет извергать необработанные текстовые пароли через ваши журналы, в ответах, в ваше программное обеспечение для мониторинга и, возможно, в сомнительные руки.

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

Конечно, все это сводится к хорошему дизайну программного обеспечения, но в мире , где регулирование персональных данных постоянно ужесточается , вы никогда не можете быть слишком осторожны, и почти всегда можно предоставлять ценные сообщения об ошибках без ущерба для конфиденциальности клиентов.

Эмпирическое правило: Не используйте конфиденциальную информацию в сообщениях об исключениях.

Однако вам нужно беспокоиться не только о конфиденциальности клиентов. Плохие актеры повсюду.

пытаясь защитить данные ваших клиентов, защитите свои серверы от хакеров, создавая функции вовремя

Конспирологический мем из фильма

Допустим, вы запускаете веб-сайт, на котором пользователи предоставляют большое количество, и вы вычисляете коэффициенты этого числа. Давайте назовем ваш сайт FactoriseMe.com .

Вы собираетесь увеличить свои инвестиции в посевные проекты, но на рынке появляется конкурент, NumbersWithinNumbers.com . Вы протестировали их продукт, и хотя он работает, он не так быстр, как ваш. И клиентский опыт намного хуже.

Один из ваших разработчиков замечает, что для очень больших чисел ваша серверная служба изо всех сил пытается вычислить коэффициенты-на самом деле, для очень, очень больших чисел служба тратит все 180 секунд на хруст чисел и просто тайм-аут.

Вы решаете продолжать улучшать свой клиентский опыт, отвечая приятной для клиента ошибкой: “Время ожидания калькулятора истекло во время вычисления коэффициентов. Извините, но эти трудные.”

Это здорово. Теперь клиент знает, почему на сайте не удалось найти какие-либо факторы. Вы заходите в свой инвестор, чтобы дать демо-версию, и вдруг ваш сайт не работает. Что случилось?

Просмотрев журналы, вы замечаете, что между 9 и 10 часами утра вы получили 1000 запросов с огромными номерами от IP-адреса, расположенного чуть дальше по дороге, недалеко от штаб-квартиры NumbersWithinNumbers. Эти запросы перегружали ваши серверные службы и приводили к сбою вашего веб-сайта. Как?

Ты показал свою слабость. Никогда не показывай свою слабость. Или, по крайней мере, никогда не раскрывайте пользователям слабые места во внутренней работе вашего программного обеспечения. Хакеры, конкуренты, тролли и интернет в целом полны людей, которые хотят сломать то, что вы построили. Да, вы должны давать своим пользователям отзывы о том, что происходит, но никогда, никогда не рассказывайте им о внутренней работе вашего программного обеспечения.

Эмпирическое правило: расскажите пользователю о том, что он может сделать, а не о том, что произошло.

Вы увидите, что это часто встречается во многих приложениях: “Пожалуйста, повторите попытку позже.” “Если это повторится, пожалуйста, свяжитесь со службой поддержки.” “Неизвестная ошибка — мы сожалеем, мы изучаем ее.”

Но сейчас все это уходит от темы, исключения, ориентированные на пользователя,-это совершенно новая банка червей. Просто не забудьте взять листок из рыцаря из книги Монти Пайтона и притвориться, что все в порядке .

Не Будьте Несовместимы Со Строгостью Вашего Кода

Все эти “делай” и “не делай” хороши в теории, но в реальном мире вам приходится иметь дело с другими командами, пишущими сервисы со всевозможными исключениями.

Скажем, команда А — это инновационная команда-они индивидуалисты, неукротимые вашим вице-президентом по инженерным вопросам. Они решают, что их код имеет радикальную политику исключений, которая бросает исключения при малейшем изменении (в следующем пункте вы увидите, почему это совсем не так радикально). С другой стороны, в команде B вы наследуете все исключения из Исключения команды B и используете их редко и только для обозначения серьезных проблем.

В конце концов, вам придется работать над проектом, используя библиотеку, созданную командой А, и обнаружить, что вы не можете полагаться на простую функцию печати без всевозможных странных исключений, закрывающих вашу службу. У вас есть крайний срок, и вы понимаете, что единственный способ завершить свою задачу-это обернуть все интерфейсы с библиотекой широкими предложениями try-catch Exception в виде предложений e и надеяться на лучшее. Не идеально.

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

Эмпирическое правило: Будьте евангелистом для хорошей обработки исключений в вашей компании. Если у вашей компании есть политика в отношении исключений, следуйте ей. Если это не так, почему бы не написать его на основе этой статьи?

Ловите много конкретных исключений

Python — прекрасный язык-он невероятно популярен, имеет массу отличных ресурсов для обучения, имеет библиотеки для всего и может делать все, что угодно, от машинного обучения до аппаратного обеспечения до веб-разработки.

Возможно, именно поэтому вы решили изучать Python, но для многих мы выбрали Python, потому что верим в ценности Python. Если это звучит культово, то это потому, что так оно и есть. Перейдите к своему терминалу и запустите Python. Затем введите import this . Вы увидите манифест Python, 19 ( или 20 ) афоризмов языка Python, написанных его создателем Тимом Питерсом.

19 афоризмов в манифесте Python – “Дзен Питона” Тима Питерса

Это действительно имеет ценность помимо развлечений, поскольку дает представление о миссии Python и дает нам советы о том, как он должен был использоваться. Важной чертой в этом случае является:

“Ошибки никогда не должны проходить бесшумно. Если только явно не заставить замолчать.”

Я надеюсь, что это было ясно изложено в вышеприведенных пунктах. Я прочитал это так: “Не ловите все исключения, если вы действительно этого не хотите, и часто ловите явные исключения.” Еще одна питоническая идиома в сообществе:

– Проси прощения, а не разрешения.”

Именно здесь Python резко отличается от традиционных парадигм C, Java и других традиционных языков. Способ Python обрабатывать процессы с исключениями-это просто пойти на это и обрабатывать исключения, если они возникают.

В C вы можете писать строки и строки кода, чтобы проверить наличие всех предварительных условий перед записью в файл — например, существует ли файл, есть ли у процесса разрешения на запись в файл и т. Д. Было бы плохой практикой не обращать внимания на все эти случаи и просто писать прямо в файл.

В Python все совсем наоборот. На самом деле, в некоторых случаях гораздо эффективнее сначала попробовать, а потом обрабатывать исключения. В приведенном выше примере, хотя вполне возможно, что права доступа к файлам неверны (или какая-то другая проблема), это крайний случай. В большинстве случаев вы будете писать в файл, который существует, и у вас есть разрешение на доступ, так зачем тратить ценную вычислительную мощность на проверку прав доступа к файлам?

Питонический способ справиться с этим заключается в том, чтобы обернуть запись в try-catch и перехватывать только конкретные исключения/крайние случаи, которые могут возникнуть, и обрабатывать их соответствующим образом. В большинстве случаев запись будет успешной, и любая логика в блоке catch будет полностью пропущена. В случае, если что-то пойдет не так, мы все равно справимся с этим.

Проверьте этот поток StackOverflow для еще нескольких примеров.

Эмпирическое правило: не тратьте слишком много времени на проверку предварительных условий, когда вместо этого вы можете предсказать и поймать конкретные исключения (т. Е. Попросить прощения, а не разрешения).

Но Как Насчет Исключений Для Клиентов?

Исключения для клиентов велики, но очень разные.

Часть 2 этого руководства будет посвящена тому, как исключения, обращенные к клиенту, отличаются от внутренних исключений, и будет включать некоторые советы по созданию простой и эффективной структуры исключений. Следуйте за мной на codementor, medium или подпишитесь на мой substack , чтобы следить за обновлениями для части 2.