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

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

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

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

Is there some way in Python to capture KeyboardInterrupt event without putting all the code inside a tryexcept statement?

I want to cleanly exit without trace if user presses Ctrl+C.


回答 0

是的,您可以使用模块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()

Yes, you can install an interrupt handler using the module signal, and wait forever using a 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()

回答 1

如果您只想不显示回溯,则使代码如下所示:

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

If all you want is to not show the traceback, make your code like this:

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


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

(Yes, I know that this doesn’t directly answer the question, but it’s not really clear why needing a try/except block is objectionable — maybe this makes it less annoying to the OP)


回答 2

设置自己的信号处理程序的另一种方法是使用上下文管理器来捕获异常并忽略它:

>>> 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
... 
>>>

这将删除tryexcept块,同时保留一些明确的说明。

这还允许您仅在代码的某些部分中忽略中断,而不必每次都设置和重置信号处理程序。

An alternative to setting your own signal handler is to use a context-manager to catch the exception and ignore it:

>>> 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
... 
>>>

This removes the tryexcept block while preserving some explicit mention of what is going on.

This also allows you to ignore the interrupt only in some portions of your code without having to set and reset again the signal handlers everytime.


回答 3

我知道这是一个古老的问题,但是我首先来到这里,然后发现了该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处理程序捕获异常,调用回调,然后重新引发该exceptions。但是您可以采取不同的方法。)

有关更多信息,请参见:

I know this is an old question but I came here first and then discovered the atexit module. I do not know about its cross-platform track record or a full list of caveats yet, but so far it is exactly what I was looking for in trying to handle post-KeyboardInterrupt cleanup on Linux. Just wanted to throw in another way of approaching the problem.

I want to do post-exit clean-up in the context of Fabric operations, so wrapping everything in try/except wasn’t an option for me either. I feel like atexit may be a good fit in such a situation, where your code is not at the top level of control flow.

atexit is very capable and readable out of the box, for example:

import atexit

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

atexit.register(goodbye)

You can also use it as a decorator (as of 2.6; this example is from the docs):

import atexit

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

If you wanted to make it specific to KeyboardInterrupt only, another person’s answer to this question is probably better.

But note that the atexit module is only ~70 lines of code and it would not be hard to create a similar version that treats exceptions differently, for example passing the exceptions as arguments to the callback functions. (The limitation of atexit that would warrant a modified version: currently I can’t conceive of a way for the exit-callback-functions to know about the exceptions; the atexit handler catches the exception, calls your callback(s), then re-raises that exception. But you could do this differently.)

For more info see:


回答 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)

You can prevent printing a stack trace for KeyboardInterrupt, without try: ... except KeyboardInterrupt: pass (the most obvious and propably “best” solution, but you already know it and asked for something else) by replacing sys.excepthook. Something like

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)

回答 5

我尝试了每个人提出的建议解决方案,但我必须自己临时编写代码才能真正起作用。以下是我的即兴代码:

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

I tried the suggested solutions by everyone, but I had to improvise code myself to actually make it work. Following is my improvised code:

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

回答 6

如果有人正在寻找快速的最小解决方案,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted

If someone is in search for a quick minimal solution,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted