Комментарии 149
Исключения, как пресловутое goto, рвут структуру программы.
И это действительно так. В критическом ПО, например, они запрещены к использованию как и goto на уровне стандартов на кодирование.
Расшифрую: у разных языков могут быть разные подходы.
Так в слайдах и показывают, как реализовать Result на питоне.
Только смысл, если аннотация типов опциональная и любой дятел тебе может вернуть вместо Err() что попало, включая None.
Часто очень удобно, когда вызываешь какие-то библиотеки неизвестно, что и где пойдет не так, шансы что что-то пойдет не так очень велики. Тогда очень удобно поймать все исключения и напечатать информацию в лог например.
Для простых скриптов, вообще удобно ничего не делать в смысле обработки, а в случае исключения просто прочитать информацию об исключении и стек вызовов, когда скрипт завершится аварийно.
В противоположном случае, без исключений пришлось бы передавать результат через всю цепочку вызовов.
Или я что-то не так понимаю?
try:
# some code
except:
pass
Но это не очень хороший код.
А в в других случаях программа упадет.
Добавил: пардон, кстати, полностью с Вами согласен. Невнимательно прочитал коммент.
Нет, ну вот пример
div = divide(10, 20);
// что дальше?
Просто я вижу тут два сценария:
Первый, автор просто делает unwrap() и соглашается с паникой если значения нет.
Второй, автор делает if div.Ok() ...
тогда он обработал ситуацию. Заставить писать Else это конечно не заставит, но это не "забыл обработать", а совершенно сознательно проигнорировал.
Ну тут должен помогать компилятор(интерпретатор?)
error: unused `std::result::Result` that must be used
--> src/main.rs:10:5
|
10 | emailNewReport();
| ^^^^^^^^^^^^^^^^^
|
= note: this `Result` may be an `Err` variant, which should be handled
Резалты как раз нужны для того, чтобы не забыть обработать ошибку, а не наоборот. Они направлены на увеличение безопасности и уверенности, что все кейзы покрыты, а не на уменьшение.
Основная сила в том что по записи foo = bar()
можно судить, может ли в этом месте произойти ошибка, и если да, то какая именно.
Черт, видимо ни у кого кроме меня не бывает проблем, что человек не знал, что в некотором месте может возникнуть исключение и не предусмотрел никакой обработки. В таком случае пожалуй стоит действительно замолчать и ничего не писать. Порадуюсь просто за таких людей молча.
Ошибки в обоих случаях отдаются наверх. Есть два паттерна передачи ошибки:
raise/except
result(Ok|Err)
Оба их них позволяют вернуть результат и сообщить об ошибке, оба из них избегают анти-паттерна magic value (-1 как ошибка). Но!
Если мы возвращаем результат, а ошибку raise'им, то может оказаться, что вышестоящий код забыл обработать эту ошибку. Мог, но не обработал. Это анти-паттерн, т.к. мы вынуждены использовать обработчик верхнего уровня (который не знает контекста). Важно, что при отладке может оказаться так, что exception'а ни разу не будет, а в редких случаях в продакшене будет.
Альтернатива: мы возвращаем Result, который обрабатывающий код обязан "unwrap". Если он его не unwrap, у него фейлится всё (в т.ч. код без ошибок), т.к. Result нельзя использовать напрямую (но можно вернуть!). Человек, который вынужден развернуть Result явно отвечает на вопрос, что делать с Err.
… точнее, такое происходит в Rust, который не скопилируется, если нет ответа. В Python можно просто проигнорировать, увы. Чтобы не дать проигнорировать, эта библиотека делает так, что "проигноировать" нельзя, надо ответить что делать:
а) Падать (явно)
б) Заменить результат на None
в) Вернуть err вверх по стеку.
Ключевая разница с raise/except в том, что "явное лучше неявного". Вызывая функцию мы не знаем, будет у нас raise или нет. Вызывая что-то с Result в качестве возвращаемого значения мы точно знаем, что "тут может быть ошибка" (а есть функции, в которых ошибки быть не может, например, def x(): return 42
). Анти-паттерн в exception'ах в том, что они неявные. Глядя на функцию мы не можем предсказать, какие exception'ы она вызовет. Хуже, часто даже автор функции не знает этого (т.к. exception'ы могут быть в любом месте от любой функции).
а есть функции, в которых ошибки быть не может, например, def x(): return 42
RangeError: Maximum call stack size exceeded
Кстати, отличный пример в моём споре с людьми про то, что чистые функции могут иметь side effects, т.е. лямбда-исчисление — очень грубая апроксимация работы компьютеров.
В принципе, я согласен, что это "неожиданная ошибка". Но, вопрос: а что программа может сделать в такой ситуации?
Вот у вас в коде:
try:
foo()
except RecursionError:
handle_recursion_error()
Оно же сфейлится на вызове handle_recursion_error
. Более того, если мы на пару уровней вверх прокинем, то там будет то же самое, потому что 2-3 вызова по стеку мы во время обработки ошибки всё равно сделаем, а на третьем нас будет ждать RE.
Что делать с эксепшнов во время обработки эксепшна?
Ловить уровнем выше.
Что делать, если процессор перегрелся во время очередного вызова функции?
Он сам знает что делать — троттлиться.
Что делать с космическими лучами, меняющими значение оперативной памяти?
Дублировать и экранировать.
Ловить уровнем выше.
Основная проблема эксепшнов — уровень выше может не ловить тот эксепшн, который выпал. И не потому, что он хотел его отдать коду выше, а потому что не знал про его существование.
Конкретный пример у меня: был код, который вызывал некий код, все эксепшны которого наследовались от RpcException. Ну я ничтоже сумнящеся написал catch (RpcException ex)
. И все работало нормально, пока через полгодика другой коллега не обновил либу. И тоже все работало хорошо, но через какое-то время начало падать. После инвестигейта, занявшего какое-то время, стало понятно, что в новой версии добавился RpcUnknownException
, который не наследуется от того, и который никак не обрабатывается. Вешать глобальный хэндлер "лови любые необработанные эксепшоны" мы не хотели, т.к. это маскировало бы фатальные ошибки, которые должны вести к крашу приложения (поведение "паника").
Я бы очень хотел в таком месте получить ошибку компиляции, а не молчаливое "ну что ж, прокину тогда эксепшон выше".
Проверяемые исключения это и есть что-то вроде резалтов, но более неудобное.
Скорее наоборот.
Собственно, поэтому в джаве они особой славы не снискали.
Там бы и резалты не снискали. А объясняется всё просто: проблема с появлением неизвестного исключения в новой версии зависимости встречается настолько редко, и решается настолько просто, что оно для многих не стоит лишней писанины с перечислением всех исключений. Думаете ручная раскрутка стека в виде резалтов снискала бы популярность?
Скорее наоборот.
Я поработал и с тем, и с другим. Возможность `var foo = Bar()` и не гадать, может ли тут быть какая-то ошибка или нет бесценна.
Там бы и резалты не снискали.
Согласен. Потому что без нормальных АДТ и паттерн матчинга делать нечего.
Думаете ручная раскрутка стека в виде резалтов снискала бы популярность?
1. В резалтах нет никакой раскрутки стека
2. Резалты легко прокидываются наверх, если есть необходимость. В том же расте достаточно написать `?` в конце выражения, чтобы ошибку прокинуть наверх. Только вот мы явно видим, что может пойти не так.
Вопросы нового исключения очень даже актуальны для любого разрабатываемого софта. Если конечно экосистема состоит из кучи либ с мажорной версией за десяток это менее актуально. Только вот кроме либ есть еще прикладной софт, и новый эксепшон может добавить парень в соседней команде, а не только либописатель.
Возможность var foo = Bar()
и не гадать, может ли тут быть какая-то ошибка или нет бесценна.
Проверяемые исключения позволяют вам не гадать.
В резалтах нет никакой раскрутки стека
Именно. Поэтому раскручивать приходится вручную.
Резалты легко прокидываются наверх, если есть необходимость.
Это необходимость в 90% случаев. Если вы, конечно не поклонник огромных функций и тривиальных приложений.
Только вот мы явно видим, что может пойти не так.
Выше я показал, что пойти не так может что угодно. И даже в этом случае приложение не должно падать. Если у вас, конечно, не консольная утилита, выполняющая одну единственную функцию.
Проверяемые исключения позволяют вам не гадать.
Это необходимость в 90% случаев. Если вы, конечно не поклонник огромных функций и тривиальных приложений.
И как понять, человек в этом месте забыл проверить ошибку или решил её прокинуть наверх?
Выше я показал, что пойти не так может что угодно. И даже в этом случае приложение не должно падать. Если у вас, конечно, не консольная утилита, выполняющая одну единственную функцию.
Есть ошибки а есть панкики. В случае паники упасть часто наилучший способ, потому что делать какие-то действия в невалидном стейте опасно.
Еще одна проблема эксепшнов — плохая композиция. Например, если я хочу сделать 10 параллельных запросов, а потом сагрегировать результаты, мне надо надеяться, что автор веб-фреймворка предусмотрел возможность этого, и какой-нибудь `WhenAll` позволяет получить результаты и ошибки. А если нет, то упс.
И как понять, человек в этом месте забыл проверить ошибку или решил её прокинуть наверх?
А как проверить подумал человек, когда писал код, или механически написал то, что от него потребовал компилятор?
Есть ошибки а есть панкики.
Есть лишь исключительные ситуации. А появление паник — следствие протекающей абстракции "ошибок".
В случае паники упасть часто наилучший способ, потому что делать какие-то действия в невалидном стейте опасно.
А давайте это вызывающий код будет решать опасно ему дальше работать или нет?
я хочу сделать 10 параллельных запросов, а потом сагрегировать результаты
Какое это имеет отношение к теме исключений?
мне надо надеяться, что автор веб-фреймворка предусмотрел возможность этого
И какое отношение имеет веб фреймворк ко многозадачности?
А как проверить подумал человек, когда писал код, или механически написал то, что от него потребовал компилятор?
Потому что он явно должен это написать. Например, unwrap(), чтобы сказать «хрен с ней с ошибкой, дай значение». И это будет видно на ревью.
Есть лишь исключительные ситуации. А появление паник — следствие протекающей абстракции «ошибок».
Нет, есть «все плохо, но мы знаем что с этим делать» и «все плохо и мы не знаем, что делать». Это прицнипиально разные вещи. В тот же дуднете поэтому в стандартной либе есть Exception и ApplicationException, который является подмножеством первых.
А давайте это вызывающий код будет решать опасно ему дальше работать или нет?
Нет, не давайте.
Какое это имеет отношение к теме исключений?
Такое, что мне интересно, как исключения работают в таком случае. По-моему опыту, не очень хорошо.
Потому что он явно должен это написать.
Мою ремарку про механическое написание вы опять проигнорировали.
Например, unwrap(), чтобы сказать «хрен с ней с ошибкой, дай значение». И это будет видно на ревью.
Как и то, что в проверяемых исключениях точно так же нужно указать "хрен с ней с ошибкой, хочу чтобы скомпилилось", что тоже будет видно на ревью.
Нет, есть «все плохо, но мы знаем что с этим делать» и «все плохо и мы не знаем, что делать».
Это не вызываемому коду решать.
Такое, что мне интересно, как исключения работают в таком случае.
Отлично работают.
Мою ремарку про механическое написание вы опять проигнорировали.
С тем же успехом можно механически писать throw new Exception()
в рандомных участках кода.
Это не вызываемому коду решать.
Нет, вызывающий код ничего не знает про эту проблему, и знать не должен. В этом и отличие паники от ошибки. Про ошибку можно сообщить наверх. Про панику нельзя, потому что это нарушение внутреннего инварианта, с которым сделать ничего нельзя. Внешний код про инвариант по-определению ничего не знает, иначе это страшная протечка абстракции.
Отлично работают.
нет
с которым сделать ничего нельзя
Можно. Я выше приводил пример.
Пример нерелевантен. Зачем предпочитать менее удобный инструмент без монадической обработки и явного хэндлинга более удобному мне так и не стало понятно.
Их не надо ловить
А давайте разработчик прикладного решения, сам решит надо их ловить или нет, а не разработчик какой-то библиотечки где-то в глубине зависимостей? Вы когда-нибудь делали настраиваемую пользователем логику? Систему подключаемых плагинов? Да хотя бы многозадачный веб-сервер?
Занятно. Можно ещё попробовать обработать ситуацию когда ваше приложение убил OOM-киллер.
Напоминает дискуссию про предложенные новые исключения в c++.
то может оказаться, что вышестоящий код забыл обработать эту ошибку
Это как? Слова я понимаю, но представить как это сделать в Python не могу. Возможно я что-то не знаю?
Так в статье именно передача наверх. Да, без исключений, но наверх. Так в чем польза передать наверх сверх result вместо того, что бы передать наверх через исключение? Если у нас опять вложенных функций по дороге, не так-то уж это удобно.
А разница в том, что exception рейзится где попало, а result — он всегда на выходе функции и надо явно ответить «что с этой ошибкой делать».
Фактически, исключения, это такой Result, для которого поведение по умолчанию (если ничего не сказано — передать дальше). Проблема в том, что это умолчание неявное и оно строго нарушает дзен питона «явное лучше неявного».
На один уровень наверх, а не "кто его знает куда" наверх.
С необходимостью на этом уровне тоже принять решение что делать с ошибкой.
Это если на один уровень вверх она ловится. Это совсем не обязательно. Недавно писал модуль, который разбирает эксель. Бросал исключения, если выяснялось, что файл ошибочен. Зачем мне ловить исключение на уровень выше? С ним там нечего делать.
Решение принимает самый верхний уровень модуля — файл разобран быть не может, отказ. По стеку это 2-3 уровня вверх. Передавать статусы на эти два уровня было бы неудобно, не вижу ни одной причины.
www.lighterra.com/papers/exceptionsharmful
Вкратце: неявность (можно легко отстрелить ногу, забыв покрыть где-нибудь какой-то случай), плохо подходит к method-chain вызовам методов (потому что эксепшны хорошо работают со statement, но плохо с expression), как следствие не очень дружит с многопотоком.
Но очень рекомендую ознакомиться со статьей. Кстати, название статьи слегка намекает на сходство с goto.
Исключения в Python теперь считаются анти-паттерном
Можно ссылку на официальную позицию? А то какой-то кликбейт. Если они считаются антипаттерном по мнению Никиты Соболева — никаких проблем, но стоит это явно указать.
Можно ещё форматирование строк через % назвать антипаттерном. А то я предпочитаю format. Или тернарные операции назовём антипаттерном. Они маскируют условия под операции присвоения. А ещё можно использование lambda объявить антипаттерном. Чисто поржать.
Долбаные модернисты, короче. Куда ни плюнь — везде находят антипаттерны. Лишь бы продвинуть свой продукт.
Я постоянно это повторяю, и повторю снова: Exception'ы идеально подходят для bad path.
Три варианта работы программы:
- happy path (хорошо на входе, хорошо на выходе)
- sad path (плохо, но мы знаем, что с этим делать)
- bad path (плохо и мы не знаем, что с этим делать)
Уничтожение bad path (перевод его в sad path) — это процесс "maturing code", перевода его в продакшен. Однако, важно, bad path всегда остаётся.
Хотите пример?
a=0
b=1
while True:
if a+1 == b:
happy()
else:
wtf()
Вопрос: Если это однопоточное приложение и happy() — чистая функция, wtf когда-либо вызовется? Согласно теории типов — нет. На практике — флипнется бит в памяти и когда-нибудь оно случится. Или while True закончится.
Вот на такие случаи и нужны exception'ы.
Если действительно у человека есть потребность писать код, который должен работать как часы даже в случае ядерной войны, то для этого лучше выбрать язык со статической типизацией. Например, Rust или Haskell, которые уже упомянули в комментах.
В тех же областях, для которых питон является хорошим выбором, исключения являются вполне удобным и разумным подходом.
Собственно, именно это я и хотел донести своим комментарием — есть ПО для интернет-магазинов и ПО для атомных реакторов.
В одном случае допустимо выдать страничку «что-то пошло не так, попробуйте позже», но разработка должна быть быстрой, код ясным, а программисты — легкозаменяемыми. И здесь питон идеален.
А во втором случае можно долго и тщательно писать и отлаживать код, а программисты могут быть штучным товаром. И нет никакого смысла тащить практики, относящиеся ко второму случаю, в язык, предназначенный для первого случая.
С другой стороны, нужно быстро набросать скрипт — пишем без всякой обработки ошибок. В случае ошибки читаем, что выбрасывает питон и чиним. Быстро и хорошо.
давайте питон в го в жабу или в спп превратим (или хаскель)
В Haskell есть какая-то похожая штука для неявной работы с контейнерами? inline-разворачивания, так сказать?
Конечно — трансформеры монад. Делаем всякие разные грязные действия, возвращающие Either (Result в статье), если хотя бы одно вернёт Left (Failure), игнорируем все последующие и возращаем этот Left. При этом локальные присваивания игнорируют Either (Result) и всегда думают, что им вернули Right (Success). Если не вернули — смотри выше.
В Haskell есть какая-то похожая штука для неявной работы с контейнерами? inline-разворачивания, так сказать?
Если я правильно здесь понимаю «разворачивание», это
do
-нотация:create :: Text -> Text -> Maybe User
create username email = do
user <- validate username email
account <- createAccount user
createUser account
Если после какой-нибудь распаковки (
<-
) получится Nothing
, в итоге будет Nothing
, и оставшиеся функции выполняться не будут, иначе create
вернёт последнее выражение. Это на самом деле всё тот же оператор bind (>>=
), только немного по-другому записанный.Мне кажется, что если и продвигать такие изменения, то через PEP и изменения конструкций языка, а не через стороннюю библиотеку. Но что-то мне подсказывает, что наследники Гвидо такое не одобрят.
Найди десять отличий:
from result.functions import pipeline
class CreateAccountAndUser(object):
"""Creates new Account-User pair."""
@pipeline
def __call__(self, username: str, email: str) -> Result['User', str]:
"""Can return a Success(user) or Failure(str_reason)."""
user_schema = self._validate_user(username, email).unwrap()
account = self._create_account(user_schema).unwrap()
return self._create_user(account)
# ...
from result.functions import pipeline
class CreateAccountAndUser(object):
"""Creates new Account-User pair."""
def __call__(self, username: str, email: str) -> User:
"""Can return a User or throws an Error(str_reason)."""
user_schema = self._validate_user(username, email)
account = self._create_account(user_schema)
return self._create_user(account)
# ...
Один вопрос. Вы пробовали это использовать?
Навскидку, вижу проблемы с тем, что 1) стандартная библиотека не поддерживает ваш подход; 2) декоратор @pipeline
может сработать неправильно в каких-то непредусмотренных сложных случаях; 3) если использовать вашу библиотеку в той мере, в какой в идеале нужно (т.е. везде), поток управления будет очень напоминать поток при обработке исключений, только обработку исключений разработчики языка могут как-то оптимизировать, а в вашей библиотеке "лапша" управления так и останется.
Очень похоже на scala.util.Try
, только в человеческой обёртке.
1 + divide(1, 0) # => mypy error: Unsupported operand types for + ("int" and "Result[float, ZeroDivisionError]")
Ок, для одного вызова за раз это легко анвраппится, а как насчет чего-то такого?
x = y(z(4, pi) * f(j, l, n)) + p(k) / d(z) + 42
Аналогичный вопрос про любые похожие ситцуации в коде, не связанном с арифметикой
Это вы только что описали принцип работы исключений :)
def foo(a, b, dictionary, host):
j = divide(a / b)
l = http.get(host).parseSmth()
k = dictionary[l]
m = divide(k / j)
...
Здесь у нас могут быть разные исключения — арифметика, сеть, отсутствует ключ. Либо мы вешаем на весь блок «except Exception», либо делаем серию except'ов на каждый тип исключения, либо вешаем персональный try на каждый опасный вызов. Второй и третий способ раздувает код и делает его нелинейным, первый я не приемлю, потому что слишком общий. Монада Result добавляет один декоратор за пределами тела функции и по .unwrap() в конец каждой строки.
Вторая проблема — два опасных divide в одной функции. Допустим мы поставили общий «except ZeroDivisionError», но теперь мы не знаем, какой из них бросил исключение. Монада Result позволяет перед вызовом unwrap() сделать rescue() и добавить подробное описание.
Разве в питоне нет стектрейса?
1) Важно то, что я не получил результат. Группируем все ексепшены и в одном месте делаем то, что нужно при неполучении результата. Отлично!
2) Кроме неполучения результата, мои действия могут отличаться в зависимости от того, какое это исключение. Группируем ексепшены по вариантам моих действий и обрабатываем так как нужно в каждом случае. Опять отлично!
Возьми любой функциональный язык и не мучайся.
Любишь красивые теоретические конструкции — Haskell.
Практический код — Scala.
Хочется императивщины с полуручным управлением памятью — Rust.
Питон, он для другого. В нем вся эта история будет жуткими костылями.
Что касается самой идеи, то она реально зачётная. И, что бы ни говорили, стопроцентно питонская. Сам недавно перепилил функцию, возвращающую булево на функцию, возвращающую кортеж в стиле (результат, почему_нет). Декоратор «safe» — просто чудо. Хотел бы или нет я это видеть в стандартной библиотеке — не уверен, но как приёмчик, который в случае чего применить в своё удовольствие — очень достойно. Спасибо.
притягивать за уши монады в Python которые только усложняют, в статье не видно чтобы они решили что-либо
на много понятней сразу словить исключения при которых можно восстановиться при помощи обычных try except сразу после вызова критичной функции чем прятать то же самое в fix/rescue в виде кучи isinstance(state, someError)
def fix(state: Exception) -> Result[int, Exception]:
if isinstance(state, ZeroDivisionError):
return Success(0)
....
return Failure(state)
Зачем Javа из Python делать? Если вам типов не хватает, то не надо Python коверкать, он не для того создавался. Python — для быстрых расчетов на скорую руку, а для продакшена используйте Java.
>Останов итерации for реализован на основе исключений
Да ладно?
Это самый натуральный кликбейт.
более того в питоне нормально использовать исключения для Control Flow
(что не рекоммендовано в большинстве языков типа Cpp, Java, C#)
посмотрите на Warning, UserWarning, DeprecationWarning
или тем более на StopIteration exceptions.
по началу мне тоже не хватало switch, pattern matching и прочего.
но у питона свой путь, и тащить сюда вещи из других языков не надо.
Как на счет фигурных скобок?
Скажите пожалуйста, как на C++/C#/Java поступают с исключениями? Если их не рекомендуют для Control Flow, что используют вместо этого? Я сам просто в основном Python знаю...
1) их реализации, очень медленные.
2) Проблем с освобождением памяти в случае исключений.
На Python обе причины не существуют для исключений.
Примеры хорошо выглядит пока вычисления последовательные. А если у меня такой код
try:
a = canFail1()
b = canFail2()
return canFail3(a, b)
except SomeError:
return None
let res = do {
let a <- canFail1()
let b <- canFail2()
let result <- canFail3(a,b)
result
}
return result.ok() // Just(result) or None
canFail3
принимает Int и String а не Result?Почему это? Все развернется в цепочку >>=
, которая остановится на первой ошибке. Только let'ы зря написал, между языками если часто переключаться можно случайно запутаться без поддержки иде.
res = do
a <- canFail1()
b <- canFail2()
result <- canFail3(a,b)
return result
Я видимо плохо знаю питон и не совсем понимаю что делает оператор <-
На питоне это будет примерно
res=canFail1()
.flatMap(lambda a:
canFail2().map(lambda b:
canFail3(a,b)
)
)
Да но этот код выглядит хуже чем код с try… catch
Было бы приятнее получить от компилятор ошибку «ты забыл вот эту ошибку обработать», когда она добавилась.
Теперь я пишу тесты. Но тесты всегда хуже проверок типов.
Вам понравится.
def scan_nmap(hostname, host_ip):
logger.debug('Timestamp: {} Hostname: {} Port: N/A Action: Nmap_scan Message: Nmap scan started'.format(datetime.datetime.now(), hostname))
nm = nmap.PortScanner()
try:
nm.scan(hosts=str(host_ip))
except Exception as error:
logger.error('Timestamp: {} Hostname: {} Port: N/A Action: Nmap_scan Message: {}'.format(datetime.datetime.now(), hostname, error))
list_http_ports = None
str_nmap_port_scan_result = ''
return list_http_ports, str_nmap_port_scan_result
По сути статьи мне сказать нечего… Мне кажется, каждый инструмент имеет область применения. Сложно придумать правила на все случаи жизни. Копья по поводу исключений ломаются десятилетиями...
Конкретно по поводу вашего кода могу сказать, что читается очень тяжело. Эти громоздкие строки логирования и названия переменных… Я считаю, что писать тип переменной в ее названии (list_
, str_
) — антипаттерн. Названия становятся громоздкими и менее читаемыми. А от отсутствия проверки типов это все равно не спасет.
И еще, вместо logger.error
лучше использовать logger.exception
— он сразу напечатает исключение и traceback.
Насчет str_, list_ — как-то всегда казалось удобным. Попробую отрефакторить аккуратно.
А как logger.exception работает? Мне надо не просто ошибку вывалить, а показать, что «При попытке получить сертификат шифрования с 2443 порта сервера example.com произошла ошибка: 'Сервер вернул полтора арбуза вместо сертификата' „
По поводу кода, я бы сказал что объявлять переменные внутри try/except это немного опасно неопределённостью. Но если их тут-же возвращать, то их и объявлять не надо.
почему бы сразу не написать
return None, ""
Исключения, как пресловутое goto, рвут структуру программыВы так и до yield, if/else, __call__ дойдете, исключения не рвут, а передают управление назад «родителю».
Но важнейшие части вашей бизнес-логики вы должны писать именно так, как я показал, чтобы обеспечить правильность работы вашей системы и облегчить поддержку в будущемКлючевое отличие которое дает ваш «фреймворк» — это заглушить/игнорировать исключение если оно не обработано, в отличие от стандартного поведения — передать исключение наверх, если оно не обработано.
И вот как раз этот подход (игнорирования исключений) ведет к не правильной работе системы.
Исключения трудно заметитьИ действительно, если вы глушите исключения, то вы их не заметите, и не узнаете, что ваша система работает не правильно.
придется почти каждую строку начинать с if something is not None
Так и не начинайте. Пишите как все
if errors:
do_something()
или not errors and happy()
или errors and logging.error("This")
В обсуждаемом примере с делением divide(0,1)=0 и это нормально, а divide(1,0)=None и должно быть обработано как исключительная ситуация. Какой такой docstring вам здесь поможет?
Если вы работаете со структурами или строками, то извините, 0 вам не вернётся никогда.
В статьях с такими заголовками ценность информации как правило равна нулю или около того.
Каждый мечтает стать собственным Геростратом, назвав антипаттерном то, что люди используют нормально годами.
Исключения в Python теперь считаются анти-паттерном