파이썬에서 SIGINT를 어떻게 캡처합니까?


535

여러 프로세스와 데이터베이스 연결을 시작하는 Python 스크립트를 작성 중입니다. 때때로 나는 Ctrl+ C신호로 스크립트를 죽이고 싶습니다. 정리하고 싶습니다.

Perl에서 나는 이것을 할 것입니다 :

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

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

파이썬에서 이것을 어떻게 아날로그로합니까?

답변:


787

다음과 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

35
노 이오 : 2 가지 이유. 먼저 SIGINT를 프로세스에 여러 방법으로 보낼 수 있습니다 (예 : 'kill -s INT <pid>'); KeyboardInterruptException이 SIGINT 핸들러로 구현되었는지 또는 실제로 Ctrl + C 프레스 만 잡는 지 확실하지 않지만 신호 핸들러를 사용하여 의도를 명시 적으로 만듭니다 (적어도 의도가 OP와 동일하면). 더 중요한 것은 신호를 사용하여 모든 것을 둘러싼 시도를 할 필요가 없으며 응용 프로그램의 구조에 따라 작곡 가능성과 일반적인 소프트웨어 엔지니어링이 될 수 있습니다.
Matt J

35
예외를 포착하는 대신 신호를 트랩하려는 이유의 예. 프로그램을 실행하고 출력을 로그 파일로 리디렉션한다고 가정합니다 ./program.py > output.log. Ctrl-C 를 누르면 모든 데이터 파일이 플러시되고 깨끗한 것으로 표시되어 알려진 정상 상태로 남아 있음을 확인함으로써 프로그램이 정상적으로 종료되기를 원합니다. 그러나 Ctrl-C는 파이프 라인의 모든 프로세스에 SIGINT를 전송하므로 program.py가 최종 로그 인쇄를 마치기 전에 쉘이 STDOUT (현재 "output.log")을 닫을 수 있습니다. 파이썬은 "파일 객체 소멸자에서 닫기에 실패했습니다 : sys.excepthook에 오류가 있습니다 :"라고 불평합니다.
노아 Spurrier

24
signal.pause ()는 Windows에서 사용할 수 없습니다. docs.python.org/dev/library/signal.html
May Oakes

10
signal.pause () 사용을위한 -1 유니콘은 실제 작업을 수행하는 대신 그러한 차단 호출을 기다려야한다고 제안합니다. ;)
Nick 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가 실행되어야하는 모든 상황에서 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


StopIterationctrl-C를 눌렀을 때 가장 안쪽 루프를 끊기 위해 a 를 던질 수도 있습니다 .
Theo Belaire 1

@TheoBelaire StopIteration을 던지는 대신 iterable을 매개 변수로 허용하고 신호 처리기를 등록 / 해제하는 생성기를 만들 것입니다.
Udi

28

예외 를 잡아서 CTRL+ C를 처리 할 수 ​​있습니다 KeyboardInterrupt. 예외 처리기에서 정리 코드를 구현할 수 있습니다.


21

파이썬 문서에서 :

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_gracefully는 AS CTRL+ 용 c핸들러

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

4
발생하지 않는 물건을 제외하고 만 사용해야합니다. 이 경우 KeyboardInterrupt가 발생합니다. 따라서 이것은 좋은 구조가 아닙니다.
Tristan

15
@TristanT 다른 언어에서는 그렇습니다. 그러나 파이썬에서는 예외가 아닙니다. 실제로 흐름 제어를 위해 예외를 사용하는 것이 파이썬에서 좋은 스타일로 간주됩니다 (적절한 경우).
Ian Goldby

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)과을 SIGTERM( kill <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는 종종 다른 답변에서 언급 한 것처럼 충분합니다.
Thomas Devoogdt

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

개인적으로 차단하는 표준 소켓 (IPC) 모드를 사용하고 있었기 때문에 try / except KeyboardInterrupt를 사용할 수 없었습니다. 그래서 SIGINT는 신호를 받았지만 소켓에서 데이터를 수신 한 후에 만 ​​왔습니다.

신호 처리기 설정은 동일하게 작동합니다.

반면에 이것은 실제 터미널에서만 작동합니다. 다른 시작 환경은 Ctrl+를 받아들이지 C않거나 신호를 미리 처리 하지 못할 수 있습니다 .

또한 파이썬에는 "예외"와 "BaseExceptions"가 있는데, 이는 인터프리터가 완전히 종료되어야한다는 의미가 다르기 때문에 일부 예외가 다른 예외보다 우선 순위가 높습니다 (예외는 BaseException에서 파생 됨)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.