无需尝试即可在Python中捕获键盘中断


101

Python中是否有某种方法可以捕获KeyboardInterrupt事件而不将所有代码放入try- except语句中?

如果用户按下Ctrl+,我想干净无痕地退出C

Answers:


149

是的,您可以使用模块signal安装中断处理程序,并使用threading.Event永远等待:

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上,只能使用SIGABRT,SIGFPE,SIGILL,SIGINT,SIGSEGV或SIGTERM调用signal()。A ValueError在其他情况下将被提出。”
bgporter 2010年

7
同样适用于线程。希望您永远不要这样做while True: continue。(while True: pass无论如何,这样会更整洁。)那将是非常浪费的;尝试类似的事情while True: time.sleep(60 * 60 * 24)(一次睡觉一天是完全任意的)。
克里斯·摩根

1
如果您使用的是Chris Morgan的建议time(如您import time
所愿

1
调用sys.exit(0)会为我触发SystemExit异常。如果与它结合使用,则可以使其运行良好:stackoverflow.com/a/13723190/353094
leetNightshade 2015年

2
您可以使用signal.pause()而不是重复睡眠
Croad Langshan

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变得不那么烦人了)


5
由于某些原因,这并不总是对我有用。 signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))总是如此。
哈尔·金丝雀

这不适用于使用线程的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

使用多处理库,我不确定应该在哪个对象上添加这些方法。
斯特凡

@Stéphane是什么意思?在进行多处理时,您必须在父进程和子进程中都处理信号,因为它可能在两个进程中都被触发。这实际上取决于您在做什么以及如何使用软件。
巴库里

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开始;该示例来自docs):

import atexit

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

如果您只想使其特定KeyboardInterrupt,那么另一个人对此问题的答案可能会更好。

但是请注意,该atexit模块只有约70行代码,并且创建类似版本以不同方式对待异常(例如将异常作为参数传递给回调函数)并不难。(这样做的局限性是atexit需要修改后的版本:目前,我无法为exit-callback-functions知道异常的方法;atexit处理程序捕获异常,调用回调,然后重新引发该例外。但是您可以采取不同的方法。)

有关更多信息,请参见:


atexit
不适

在这里为KeyboardInterrupt工作(python 3.7,MacOS)。也许是针对特定平台的怪癖?
Niko Nyman

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
亚历克斯

7
这根本不是真的。KeyboardInterrupt异常是在中断处理程序期间创建的。SIGINT的默认处理程序会引发KeyboardInterrupt,因此,如果您不希望这种行为,您所要做的就是为SIGINT提供一个不同的信号处理程序。您是正确的,因为只能在try / except中处理异常,但是在这种情况下,您可以避免从一开始就引发异常。
马特

1
是的,我知道发布后大约三分钟,当kotlinski的答案出现时;)

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.