Зачем здесь обрабатывать ошибку SystemExit

SystemExit обрабатывает ошибку, которую вызывает sys.exit(). То есть, по идее, без вызова функции exit() этой ошибки не будет. Я делаю телеграм бота, использую вот этот шаблон https://github.com/Tishka17/tgbot_template/blob/master/src/tgbot/cli.py, в котором встречается except (KeyboardInterrupt, SystemExit). Зачем нужно обрабатывать SystemExit, если он все равно не прилетает? Могу предположить, что systemd когда останавливает *.service вызывает в программе SystemExit. Хотелось бы понять почему и получить best practice — почему не написать просто except, ловя все ошибки, хоть и нужно ловить только одну по pep? Ведь остановка бота может быть спровоцирована не только этими ошибками


Ответы (1 шт):

Автор решения: CrazyElf

Хотелось бы понять почему и получить best practice — почему не написать просто except, ловя все ошибки, хоть и нужно ловить только одну по pep?

    except (KeyboardInterrupt, SystemExit):
        logger.error("Bot stopped!")

Ну вот смотрите - в данном куске кода проверяется, что было "легальное" завершение работы бота - он был прерван с клавиатуры, либо сам для себя вызвал выход через sys.exit. И об этом пишется в лог. Вы потом открываете лог, смотрите - ага, в такое-то время бот был остановлен по такой-то причине. Удобно.

Зачем нужно обрабатывать SystemExit, если он все равно не прилетает?

Ну, это диалектический вопрос. С одной стороны, промышленное программирование учит нас не делать лишних фич, которые нам не нужны в данный момент. Но в данном случае это ведь шаблон, универсальная заготовка. Откуда шаблону знать - есть конкретно в вашем боте использование sys.exit или нет? Да и вы на самом деле не можете быть на 100% уверены, что у вас он не появится в коде когда-нибудь. И тогда вы с одной стороны вроде бы сможете залогировать это событие там, где вы собственно вызываете sys.exit. Но что если такое место будет не одно? Удобно ведь сделать логирование не во всех местах, где вызывается sys.exit, а только в одном, по принципу DRY. И вот это и есть то самое место, где это лучше всего сделать.

Хотелось бы понять почему и получить best practice — почему не написать просто except, ловя все ошибки, хоть и нужно ловить только одну по pep? Ведь остановка бота может быть спровоцирована не только этими ошибками

Вот именно поэтому и не рекомендуется ловить все исключения без разбору. Вы предлагали бы написать так?

    except Exception:
        logger.error("Bot stopped!")

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

Можно, конечно, логировать любые ошибки:

    except Exception as ex:
        logger.error("Произошла какая-то ошибка!", ex)

Но тогда в случаях, когда бот был остановлен через Ctrl+C или через ваш же sys.exit, в лог будет писаться совершенно лишняя информация. Такая остановка не является ошибкой, зачем вам лишняя информация, забивающая лог и отвлекающая ваше внимание? Потом вы захотите найти по логу поиском, не было ли ошибок, будете там находить вот эти записи и пытаться понять - ошибка ли это или нормальное завершение. Зачем вам лишняя ручная работа?

Так что best practice тут будет такая - добавить здесь или где-то уровнем выше (если он у вас есть) обработчик всех исключений, но сделать это не вместо обработки конкретных исключений, а в дополнение к ним:

    except (KeyboardInterrupt, SystemExit):
        logger.error("Bot stopped!")
    except Exception as ex:
        logger.error("Произошла какая-то ошибка!", ex)

После этого вы легко сможете найти в логе реальные ошибки, требующие вашего вмешательства, просто поиском по слову "ошибка". Удобно.

→ Ссылка