【问题标题】:Capture keyboardinterrupt in Python without try-except在 Python 中捕获键盘中断而不使用 try-except
【发布时间】:2011-05-11 10:58:45
【问题描述】:

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

如果用户按下 Ctrl+C,我想干净利落地退出。

【问题讨论】:

    标签: python try-catch keyboardinterrupt


    【解决方案1】:

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

    【讨论】:

    • 请注意,信号模块存在一些特定于平台的问题——不应该影响此海报,但“在 Windows 上,signal() 只能使用 SIGABRT、SIGFPE、SIGILL、SIGINT 调用、SIGSEGV 或 SIGTERM。在任何其他情况下都会引发 ValueError。"
    • 也适用于线程。不过,我希望你永远不要这样做while True: continue。 (无论如何,在那种风格中,while True: pass 会更整洁。)那会非常浪费;试试while True: time.sleep(60 * 60 * 24) 之类的东西(一次睡一天完全是随意的数字)。
    • 如果你正在使用 Chris Morgan 的建议,即使用 time(你应该这样做),不要忘记 import time :)
    • 调用 sys.exit(0) 会为我触发 SystemExit 异常。如果将其与以下内容结合使用,则可以使其正常工作:stackoverflow.com/a/13723190/353094
    • 你可以使用 signal.pause() 代替反复休眠
    【解决方案2】:

    如果您只想不显示回溯,请将您的代码设为:

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

    【讨论】:

    • 出于某种原因,这并不总是适合我。 signal.signal( signal.SIGINT, lambda s, f : sys.exit(0)) 总是这样。
    • 这并不总是适用于诸如使用线程的 pygtk 之类的东西。有时 ^C 只会杀死当前线程而不是整个进程,因此异常只会通过该线程传播。
    • 还有另一个关于 Ctrl+C 和 pygtk 的问题:stackoverflow.com/questions/16410852/…
    【解决方案3】:

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

    >>> 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 块,同时保留对正在发生的事情的一些明确提及。

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

    【讨论】:

    • 很好,这个解决方案在表达目的而不是处理信号方面似乎更直接。
    • 使用多处理库,我不确定应该在哪个对象上添加这些方法......有什么线索吗?
    • @Stéphane 你是什么意思?在处理多处理时,您必须同时处理父进程和子进程中的信号,因为它可能在两者中都被触发。这实际上取决于您在做什么以及如何使用您的软件。
    【解决方案4】:

    我知道这是一个老问题,但我先来到这里,然后发现了atexit 模块。我还不知道它的跨平台跟踪记录或完整的警告列表,但到目前为止,它正是我在尝试处理 Linux 上的 post-KeyboardInterrupt 清理时所寻找的。只是想以另一种方式解决问题。

    我想在 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 的限制将保证修改版本:目前我无法设想退出回调函数了解异常的方法;atexit 处理程序捕获异常,调用您的回调( ),然后重新引发该异常。但您可以以不同的方式执行此操作。)

    更多信息见:

    【讨论】:

    • atexit 不适用于 KeyboardInterrupt (python 3.7)
    • 在这里为 KeyboardInterrupt 工作(python 3.7,MacOS)。也许是特定于平台的怪癖?
    • 可以确认 atexit 适用于 MacOS 和 Ubuntu 18.04 的 python 3.7 和 3.8
    【解决方案5】:

    您可以通过替换sys.excepthook 来阻止打印KeyboardInterrupt 的堆栈跟踪,而无需try: ... except KeyboardInterrupt: pass(最明显且可能是“最佳”解决方案,但您已经知道并要求其他内容)。类似的东西

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

    【讨论】:

    • 如果用户按下 ctrl-c,我想要干净的退出而不留痕迹
    • 这根本不是真的。 KeyboardInterrupt 异常是在中断处理程序期间创建的。 SIGINT 的默认处理程序会引发 KeyboardInterrupt,因此如果您不想要这种行为,您所要做的就是为 SIGINT 提供不同的信号处理程序。您是正确的,因为只能在 try/except 中处理异常,但是在这种情况下,您可以防止异常从一开始就被引发。
    • 是的,我在发帖后大约三分钟才知道,当时 kotlinski 的回答开始了 ;)
    【解决方案6】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-14
      • 2022-11-29
      • 1970-01-01
      • 2022-11-27
      • 1970-01-01
      • 2013-09-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多