Захват прерывания клавиатуры в Python без try-except

102

Есть ли способ в Python захватить KeyboardInterruptсобытие, не помещая весь код в оператор try- except?

Я хочу выйти без следа, если пользователь нажмет Ctrl+ C.

Alex
источник

Ответы:

150

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

import signal
import sys
import time
import threading

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
forever = threading.Event()
forever.wait()
Йохан Котлински
источник
10
Обратите внимание, что есть некоторые проблемы с сигнальным модулем, связанные с платформой - не должны влиять на этот плакат, но «В Windows signal () можно вызывать только с помощью SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV или SIGTERM. Ошибка ValueError будет поднят в любом другом случае ".
bgporter
7
Также хорошо работает с потоками. Я надеюсь, что ты никогда этого не сделаешь while True: continue. (В while True: passлюбом случае, в таком стиле было бы аккуратнее.) Это было бы очень расточительно; попробуйте что-нибудь вроде while True: time.sleep(60 * 60 * 24)(спать по день - это совершенно произвольная цифра).
Крис Морган,
1
Если вы используете предложение Криса Моргана об использовании time(как следует), не забудьте import time:)
Seaux
1
Вызов sys.exit (0) вызывает для меня исключение SystemExit. Вы можете заставить его работать хорошо, если вы используете его в сочетании с этим: stackoverflow.com/a/13723190/353094
leetNightshade
2
Вы можете использовать signal.pause () вместо того, чтобы постоянно спать
Croad Langshan 05
36

Если все, что вы хотите, - это не отображать трассировку, сделайте свой код следующим образом:

## all your app logic here
def main():
   ## whatever your app does.


if __name__ == "__main__":
   try:
      main()
   except KeyboardInterrupt:
      # do nothing here
      pass

(Да, я знаю, что это не дает прямого ответа на вопрос, но не совсем понятно, почему необходимость в блоке try / except нежелательна - возможно, это делает его менее раздражающим для OP)

bgporter
источник
5
У меня почему-то не всегда получается. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))всегда делает.
Hal Canary
Это не всегда работает с такими вещами, как pygtk, которые используют потоки. Иногда ^ C просто уничтожает текущий поток, а не весь процесс, поэтому исключение будет распространяться только через этот поток.
Sudo Bash
Есть еще один вопрос SO конкретно о Ctrl + C с pygtk: stackoverflow.com/questions/16410852/…
bgporter
30

Альтернативой настройке собственного обработчика сигналов является использование диспетчера контекста для перехвата исключения и его игнорирования:

>>> class CleanExit(object):
...     def __enter__(self):
...             return self
...     def __exit__(self, exc_type, exc_value, exc_tb):
...             if exc_type is KeyboardInterrupt:
...                     return True
...             return exc_type is None
... 
>>> with CleanExit():
...     input()    #just to test it
... 
>>>

Это удаляет блок try- exceptпри сохранении некоторых явных упоминаний о том, что происходит.

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

Бакуриу
источник
1
хорошо, это решение кажется более прямым в выражении цели, а не в работе с сигналами.
Seaux
Используя многопроцессорную библиотеку, я не уверен, к какому объекту мне добавить эти методы ... есть подсказка?
Стефан
@ Стефан Что ты имеешь в виду? При работе с многопроцессорностью вам придется иметь дело с сигналом как в родительском, так и в дочернем процессах, поскольку он может быть запущен в обоих. Это действительно зависит от того, что вы делаете и как ваше программное обеспечение будет использоваться.
Bakuriu
8

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

Я хочу выполнить очистку после выхода в контексте операций Fabric, поэтому перенос всего в try/ exceptне был для меня вариантом. Я чувствую, что atexitможет хорошо подойти в такой ситуации, когда ваш код не находится на верхнем уровне потока управления.

atexit очень способный и читаемый прямо из коробки, например:

import atexit

def goodbye():
    print "You are now leaving the Python sector."

atexit.register(goodbye)

Вы также можете использовать его в качестве декоратора (начиная с версии 2.6; этот пример взят из документации):

import atexit

@atexit.register
def goodbye():
    print "You are now leaving the Python sector."

Если вы хотите сделать это специфичным KeyboardInterruptтолько для себя, ответ на этот вопрос, вероятно, будет лучше.

Но обратите внимание, что atexitмодуль содержит всего ~ 70 строк кода, и было бы несложно создать аналогичную версию, которая по-другому обрабатывает исключения, например, передает исключения в качестве аргументов функциям обратного вызова. (Ограничение atexitэтого требует модифицированной версии: в настоящее время я не могу представить, как функции exit-callback узнают об исключениях; atexitобработчик улавливает исключение, вызывает ваши обратные вызовы, а затем повторно вызывает это исключение. Но вы могли бы сделать это по-другому.)

Для получения дополнительной информации см .:

дрифтер
источник
atexit не работает для KeyboardInterrupt (python 3.7)
TimZaman
Здесь работает KeyboardInterrupt (python 3.7, MacOS). Может быть, особенность платформы?
Нико Найман,
Может подтвердить atexitработу как для MacOS, так и для Ubuntu 18.04 для python 3.7 и 3.8
Айнз Титор
4

Вы можете предотвратить печать трассировки стека KeyboardInterruptбез try: ... except KeyboardInterrupt: pass(наиболее очевидное и, вероятно, «лучшее» решение, но вы уже знаете это и просили что-то еще), заменив sys.excepthook. Что-то вроде

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)
Ричард
источник
Мне нужен чистый выход без следа, если пользователь нажмет ctrl-c
Alex
7
Это совсем не так. Исключение KeyboardInterrupt создается во время обработки прерывания. Обработчик по умолчанию для SIGINT вызывает KeyboardInterrupt, поэтому, если вам не нужно такое поведение, все, что вам нужно сделать, это предоставить другой обработчик сигнала для SIGINT. Вы правы в том, что исключения могут обрабатываться только в try /, но в этом случае вы можете не допускать возникновения исключения в первую очередь.
Мэтт
1
Да, я узнал об этом примерно через три минуты после публикации, когда пришел ответ Котлински;)
2

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

import signal
import sys
import time

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    print(signal) # Value is 2 for CTRL + C
    print(frame) # Where your execution of program is at moment - the Line Number
    sys.exit(0)

#Assign Handler Function
signal.signal(signal.SIGINT, signal_handler)

# Simple Time Loop of 5 Seconds
secondsCount = 5
print('Press Ctrl+C in next '+str(secondsCount))
timeLoopRun = True 
while timeLoopRun:  
    time.sleep(1)
    if secondsCount < 1:
        timeLoopRun = False
    print('Closing in '+ str(secondsCount)+ ' seconds')
    secondsCount = secondsCount - 1
Рохит Джайн
источник
0

Если кто-то ищет быстрое минимальное решение,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
tejasvi88
источник