如何在Python中捕获SIGINT?


534

我正在研究启动多个进程和数据库连接的python脚本。我不时地想用Ctrl+ C信号杀死脚本,我想进行一些清理。

在Perl中,我可以这样做:

$SIG{'INT'} = 'exit_gracefully';

sub exit_gracefully {
    print "Caught ^C \n";
    exit (0);
}

如何在Python中做类似的事情?

Answers:


786

用以下方式注册您的处理程序signal.signal

#!/usr/bin/env python
import signal
import sys

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

代码改编自此处

有关更多文档,请signal参见此处。  


13
你能告诉我为什么用它代替KeyboardInterrupt异常吗?使用起来不是更直观吗?
noio 2011年

35
Noio:2个原因。首先,可以通过多种方式将SIGINT发送到您的进程(例如'kill -s INT <pid>');我不确定KeyboardInterruptException是作为SIGINT处理程序实现还是真的只捕获Ctrl + C按下,但是无论哪种方式,使用信号处理程序都会使您的意图明确(至少,如果您的意图与OP相同)。但是,更重要的是,有了信号,您不必在所有内容上都包含尝试标记即可使它们起作用,这可以或多或少地具有可组合性,并且取决于应用程序的结构,一般的软件工程制胜。
Matt J

35
为什么要捕获信号而不捕获异常的示例。假设您运行程序并将输出重定向到日志文件./program.py > output.log。当您按Ctrl-C时,您要让程序记录所有数据文件均已刷新并标记为干净以确认它们处于已知的良好状态,从而使其正常退出。但是Ctrl-C将SIGINT发送到管道中的所有进程,因此shell可以在program.py完成打印最终日志之前关闭STDOUT(现在为“ output.log”)。Python将抱怨:“文件对象析构函数关闭失败:sys.excepthook中的错误:”。
Noah Spurrier,

24
注意,signal.pause()在Windows上不可用。docs.python.org/dev/library/signal.html
May Oakes,

10
-1个使用signal.pause()的独角兽建议我必须等待这样的阻塞调用,而不是做一些实际的工作。;)
尼克T

177

您可以像对待其他异常一样将其视为异常(KeyboardInterrupt)。制作一个新文件,并从您的外壳运行以下内容,以了解我的意思:

import time, sys

x = 1
while True:
    try:
        print x
        time.sleep(.3)
        x += 1
    except KeyboardInterrupt:
        print "Bye"
        sys.exit()

22
使用此解决方案时要注意。您还应该在KeyboardInterrupt catch block:之前使用此代码signal.signal(signal.SIGINT, signal.default_int_handler),否则您将失败,因为KeyboardInterrupt不会在应触发的每种情况下触发!详细信息在这里
Velda

67

并作为上下文管理器:

import signal

class GracefulInterruptHandler(object):

    def __init__(self, sig=signal.SIGINT):
        self.sig = sig

    def __enter__(self):

        self.interrupted = False
        self.released = False

        self.original_handler = signal.getsignal(self.sig)

        def handler(signum, frame):
            self.release()
            self.interrupted = True

        signal.signal(self.sig, handler)

        return self

    def __exit__(self, type, value, tb):
        self.release()

    def release(self):

        if self.released:
            return False

        signal.signal(self.sig, self.original_handler)

        self.released = True

        return True

使用方法:

with GracefulInterruptHandler() as h:
    for i in xrange(1000):
        print "..."
        time.sleep(1)
        if h.interrupted:
            print "interrupted!"
            time.sleep(2)
            break

嵌套处理程序:

with GracefulInterruptHandler() as h1:
    while True:
        print "(1)..."
        time.sleep(1)
        with GracefulInterruptHandler() as h2:
            while True:
                print "\t(2)..."
                time.sleep(1)
                if h2.interrupted:
                    print "\t(2) interrupted!"
                    time.sleep(2)
                    break
        if h1.interrupted:
            print "(1) interrupted!"
            time.sleep(2)
            break

从这里:https : //gist.github.com/2907502


StopIteration当按下ctrl-C时,它也可能抛出a 中断最内层的循环,对吗?
Theo Belaire 2014年

@TheoBelaire我将创建一个生成器,该生成器接受一个iterable作为参数并注册/释放信号处理程序,而不仅仅是抛出StopIteration。
2014年

28

您可以通过捕获异常来处理CTRL+ 。您可以在异常处理程序中实现任何清理代码。CKeyboardInterrupt


21

从Python的文档中

import signal
import time

def handler(signum, frame):
    print 'Here you go'

signal.signal(signal.SIGINT, handler)

time.sleep(10) # Press Ctrl+c here

18

另一个片段

简称main为主要功能,并exit_gracefullyCTRL+ c处理器

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass
    finally:
        exit_gracefully()

4
您只应使用不应发生的东西除外。在这种情况下,应该发生KeyboardInterrupt。因此,这不是一个好的结构。
特里斯坦

15
@TristanT在任何其他语言中都可以,但是在Python中,异常不仅适用于不应该发生的事情。实际上,在Python中使用异常进行流控制(在适当的情况下)是一种好样式。
伊恩·戈德比

8

我改编了@udi的代码以支持多种信号(没什么花哨的):

class GracefulInterruptHandler(object):
    def __init__(self, signals=(signal.SIGINT, signal.SIGTERM)):
        self.signals = signals
        self.original_handlers = {}

    def __enter__(self):
        self.interrupted = False
        self.released = False

        for sig in self.signals:
            self.original_handlers[sig] = signal.getsignal(sig)
            signal.signal(sig, self.handler)

        return self

    def handler(self, signum, frame):
        self.release()
        self.interrupted = True

    def __exit__(self, type, value, tb):
        self.release()

    def release(self):
        if self.released:
            return False

        for sig in self.signals:
            signal.signal(sig, self.original_handlers[sig])

        self.released = True
        return True

此代码支持键盘中断调用(SIGINT)和SIGTERMkill <process>


5

Matt J的回答相反,我使用一个简单的对象。这使我有可能将处理程序解析为所有需要停止securlery的线程。

class SIGINT_handler():
    def __init__(self):
        self.SIGINT = False

    def signal_handler(self, signal, frame):
        print('You pressed Ctrl+C!')
        self.SIGINT = True


handler = SIGINT_handler()
signal.signal(signal.SIGINT, handler.signal_handler)

别处

while True:
    # task
    if handler.SIGINT:
        break

您应该使用事件,或者time.sleep()不要对变量执行繁忙循环。
OlivierM

@OlivierM这实际上是特定于应用程序的,绝对不是本示例的重点。例如,阻止呼叫或等待功能不会使CPU处于繁忙状态。此外,这只是如何完成事情的一个示例。就像其他答案中提到的那样,KeyboardInterrupts通常就足够了。
托马斯·德沃格

4

您可以使用Python的内置信号模块中的函数在python中设置信号处理程序。具体来说,该signal.signal(signalnum, handler)功能用于注册handler信号功能signalnum


3

感谢现有的答案,但补充 signal.getsignal()

import signal

# store default handler of signal.SIGINT
default_handler = signal.getsignal(signal.SIGINT)
catch_count = 0

def handler(signum, frame):
    global default_handler, catch_count
    catch_count += 1
    print ('wait:', catch_count)
    if catch_count > 3:
        # recover handler for signal.SIGINT
        signal.signal(signal.SIGINT, default_handler)
        print('expecting KeyboardInterrupt')

signal.signal(signal.SIGINT, handler)
print('Press Ctrl+c here')

while True:
    pass

3

如果您想确保清理过程完成,则可以使用SIG_IGN来添加Matt J的答案,以便进一步忽略它们,以防止清理中断。SIGINT

import signal
import sys

def signal_handler(signum, frame):
    signal.signal(signum, signal.SIG_IGN) # ignore additional signals
    cleanup() # give your process a chance to clean up
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler) # register the signal with the signal handler first
do_stuff()

0

就个人而言,我无法使用try / except KeyboardInterrupt,因为我使用的是阻塞的标准套接字(IPC)模式。因此,对SIGINT进行了提示,但仅在套接字上接收到数据之后才出现。

设置信号处理程序的行为相同。

另一方面,这仅适用于实际终端。其他启动环境可能不接受Ctrl+ C或预先处理信号。

此外,Python中还有“异常”和“ BaseExceptions”,它们在解释器需要干净退出本身的意义上有所不同,因此某些异常的优先级高于其他异常(异常是从BaseException派生的)

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.