Перехват KeyboardInterrupt в Python во время закрытия программы

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

Я пробовал использовать как блок try catch, например:

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print 'Interrupted'
        sys.exit(0)

и поймать сам сигнал (как в этот пост):

import signal
import sys

def sigint_handler(signal, frame):
    print 'Interrupted'
    sys.exit(0)
signal.signal(signal.SIGINT, sigint_handler)

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

^CInterrupted
Exception KeyboardInterrupt in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802852b90>> ignored

тогда как обработка сигнала дает либо

^CInterrupted
Exception SystemExit: 0 in <Finalize object, dead> ignored

or

^CInterrupted
Exception SystemExit: 0 in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802854a90>> ignored

Эти ошибки не только уродливы, но и не очень полезны (особенно для конечного пользователя без исходного кода)!

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


person Dan    schedule 14.01.2014    source источник
comment
Почему бы вам не заменить sys.stdout/sys.stderr? Нравится sys.stderr = open(os.devnull, 'w')?. Если вас действительно не волнует конечный результат, то это кажется очевидным решением.   -  person Bakuriu    schedule 14.01.2014
comment
Есть os._exit, но мне кажется, что это носовые демоны. Где ваш код очистки? Используете ли вы модуль atexit для тот?   -  person wim    schedule 14.01.2014
comment
@Bakuriu: хотя перенаправление stderr отключает вывод, оно также устраняет законные ошибки, с которыми пользователь может что-то сделать, например, файл не найден или хост недоступен.   -  person Dan    schedule 14.01.2014
comment
@ Дэн Это не обязательно должно быть /dev/null. Вы можете написать собственный файлоподобный объект, который скрывает только сообщения с заданным форматом.   -  person Bakuriu    schedule 14.01.2014
comment
@Bakuriu: Мне все еще кажется довольно хакерским. Я сделаю это, если придется, но я чувствую, что это должно быть встроено в язык.   -  person Dan    schedule 15.01.2014
comment
Разве ответ третьего Дэна не дает правильный ответ?   -  person Alexey    schedule 28.03.2021


Ответы (2)


Ознакомьтесь с этой веткой, в ней есть полезная информация о выходе и трассировке.

Если вас больше интересует просто убить программу, попробуйте что-то вроде этого (это также уберет ноги из-под кода очистки):

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('Interrupted')
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)
person Dan Hogan    schedule 15.01.2014
comment
Я бы рекомендовал исключить as, затем взять и повторно использовать код выхода. - person wizzwizz4; 25.07.2017
comment
да. Определенно не следует выходить с 0 на KeyboardInterrupt. Если кто-то использует ваш скрипт в скрипте, канале или чем-то еще, они будут думать, что ваш код выполняется как обычно. - person user3342816; 23.09.2019
comment
Linux обычно завершается с кодом 130: 128 + 2 tldp.org/LDP/abs/html /exitcodes.html#EXITCODESREF , Не удается найти кросс-платформенную функцию кода выхода на python. Но 1 определенно лучше, чем 0 - person user3342816; 23.09.2019
comment
Я не возражаю против лучшего кода выхода. Тем не менее, предоставленный код был скопированной вставкой из OP, и обычно, когда вы нажимаете Ctrl-C что-то, вас не волнует код выхода, поскольку что-то еще уже пошло ужасно не так. - person Dan Hogan; 24.09.2019
comment
Если пользователь вызывает программу python из скрипта bash и использует в скрипте set -e (как и должно быть), вы захотите прервать весь скрипт bash после того, как пользователь нажмет CTRL+C. Это потребует возврата ненулевого кода выхода. - person max; 13.06.2020
comment
@user3342816 чем* - person Bersan; 22.10.2020
comment
@Берсан: Спасибо! Я прочитал на grammarly.com/blog/than-then , теперь я только надо запомнить :D - person user3342816; 20.12.2020
comment
@wizzwizz4 Как получить код выхода из исключения? - person Newbyte; 01.07.2021
comment
@Newbyte try: ... except SystemExit as e: os._exit(e.code) или что-то в этом роде. - person wizzwizz4; 02.07.2021

Вы можете игнорировать SIGINT после начала выключения, вызвав signal.signal(signal.SIGINT, signal.SIG_IGN) перед запуском кода очистки.

person Dan Getz    schedule 15.01.2014