Как обработать выход из программы из-за последовательности вызовов функций
Что проверяют
Понимаете ли вы, как исключение и выход проходят через стек вызовов и на каком уровне их правильно ловить. Следом часто спрашивают, чем sys.exit() отличается от os._exit() и почему except Exception не ловит выход.
Как отвечать
Опорная мысль: если функция на пятом уровне вложенности упала, исключение поднимается по стеку, пока его кто-то не поймает. Не поймал никто, и интерпретатор печатает traceback и завершает процесс с кодом 1. Поэтому ловить стоит не в каждой функции, а там, где с ошибкой можно что-то сделать.
- нижние уровни: бросают осмысленное исключение, при переупаковке пишут
raise AppError(...) from e, чтобы не потерять исходную причину; - верхний уровень: одна точка в
main()ловит ожидаемые ошибки, пишет понятное сообщение вstderrили лог и возвращает код выхода, а запуск идёт черезsys.exit(main()); sys.exit(): не останавливает процесс сразу, а бросаетSystemExit. Он наследуетBaseException, поэтому проходит сквозьexcept Exception, но выполняетfinallyи контекстные менеджеры. Голыйexcept:его перехватит, и программа не выйдет;os._exit(code): мгновенный выход безfinally,atexitи сброса буферов. Нужен в дочернем процессе послеfork, в обычном коде нет;- очистка: ресурсы закрываются через
withиfinally, действия при выходе регистрируются вatexit; - глубокая цепочка вызовов: если процесс падает от длины самой цепочки, это
RecursionError, по умолчанию после тысячи уровней, лимит покажетsys.getrecursionlimit(). Надёжнее не поднимать его, а переписать рекурсию на цикл.
Упомяните коды выхода: 0 значит успех, ненулевой код ошибку, а sys.exit("текст") печатает строку в stderr и возвращает 1.
Где спотыкаются
Зовут sys.exit() глубоко внутри библиотечной функции. Такой код трудно переиспользовать и тестировать: вызывающий получает завершение процесса вместо исключения.
Пишут голый except: или except BaseException, который глотает SystemExit и KeyboardInterrupt, и программа перестаёт реагировать на Ctrl+C.
Ловят исключение, печатают и продолжают работу. Следующая функция падает уже на None, и настоящая причина теряется.