Python'da SIGINT'i nasıl yakalayabilirim?


535

Birkaç işlem ve veritabanı bağlantıları başlatan bir python komut dosyası üzerinde çalışıyorum. Her seferinde senaryoyu bir Ctrl+ Csinyaliyle öldürmek istiyorum ve biraz temizlik yapmak istiyorum.

Perl'de bunu yaparım:

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

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

Python'da bunun analogunu nasıl yapabilirim?

Yanıtlar:


787

İşleyicinizi şu şekilde kaydedin 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()

Kod buradan uyarlandı .

Hakkında daha fazla belge buradasignal bulunabilir .  


13
Bunun neden KeyboardInterrupt istisnası yerine kullanılmasını söyleyebilir misiniz? Kullanımı daha sezgisel değil mi?
noio

35
Noio: 2 sebep. İlk olarak, SIGINT, işleminize herhangi bir sayıda yolla gönderilebilir (örneğin, 'kill -s INT <pid>'); KeyboardInterruptException'ın bir SIGINT işleyicisi olarak uygulanıp uygulanmadığından veya gerçekten sadece Ctrl + C tuşlarını yakalayıp yakalamadığından emin değilim, ancak her iki durumda da, bir sinyal işleyici kullanmak niyetinizi açık hale getirir (en azından niyetiniz OP'lerle aynı ise). Daha da önemlisi, bir sinyal ile, çalışmalarını yapmak için her şeyi etrafına sarmak zorunda kalmazsınız, bu da uygulamanızın yapısına bağlı olarak daha fazla veya daha az bir yazılım ve genel yazılım mühendisliği kazanabilir.
Matt J

35
İstisna yakalamak yerine neden sinyali yakalamak istediğinize örnek. Programınızı çalıştırdığınızı ve çıktıyı bir günlük dosyasına yönlendirdiğinizi varsayalım ./program.py > output.log. Ctrl-C tuşlarına bastığınızda , programınızın, bilinen tüm iyi durumda kaldıklarını doğrulamak için tüm veri dosyalarının temizlendiğini ve temiz olarak işaretlendiğini kaydederek çıkmasını istersiniz. Ancak Ctrl-C bir boru hattındaki tüm işlemlere SIGINT gönderir, böylece program.py son günlüğü yazdırmayı bitirmeden kabuk STDOUT'u (şimdi "output.log") kapatabilir. Python "dosya nesnesi yıkıcısında kapatma başarısız oldu: sys.excepthook'ta hata:" şeklinde şikayet edecek.
Noah Spurrier

24
Windows'ta signal.pause () işlevinin kullanılamadığını unutmayın. docs.python.org/dev/library/signal.html
Oakes Mayıs

10
Signal.pause () kullanmak için -1 unicorns, gerçek bir iş yapmak yerine böyle bir engelleme çağrısında beklemem gerektiğini önerir. ;)
Nick T

177

Diğerleri gibi bir istisna (KeyboardInterrupt) gibi davranabilirsiniz. Ne demek istediğimi görmek için yeni bir dosya oluşturun ve kabuğunuzdan aşağıdaki içeriklerle çalıştırın:

import time, sys

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

22
Bu çözümü kullanırken dikkat edin. Bu kodu KeyboardInterrupt catch bloğundan önce de kullanmalısınız: signal.signal(signal.SIGINT, signal.default_int_handler)ya da başarısız olacaksınız, çünkü KeyboardInterrupt ateş etmesi gereken her durumda ateş etmiyor! Detaylar burada .
Velda

67

Ve bir içerik yöneticisi olarak:

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

Kullanmak:

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

İç içe işleyiciler:

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

Buradan: https://gist.github.com/2907502


Ayrıca StopIteration, bir ctrl-C tuşuna basıldığında en içteki döngüyü kırmak için a atabilir , değil mi?
Theo Belaire

@TheoBelaire Sadece bir StopIteration atmak yerine, bir yinelemeyi parametre olarak kabul eden ve sinyal işleyicisini kaydeden / serbest bırakan bir jeneratör oluşturacağım.
Udi

28

İstisnayı yakalayarak CTRL+ Cile başa çıkabilirsiniz KeyboardInterrupt. İstisna işleyicisine herhangi bir temizleme kodunu uygulayabilirsiniz.


21

Python'un belgelerinden :

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

Yine Bir Snippet

Referans mainana fonksiyonu olarak ve exit_gracefullyşekilde CTRL+ cişleyicisi

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

4
Sadece olması gerekmeyen şeyler dışında kullanmalısınız. Bu durumda KeyboardInterrupt'ın olması gerekiyor. Yani bu iyi bir yapı değil.
Tristan

15
@TristanT Başka bir dilde evet, ancak Python'da istisnalar sadece olması gerekmeyen şeyler için değildir. Aslında Python'da akış kontrolü için istisnalar kullanmak uygun bir stil olarak kabul edilir (uygun olduğunda).
Ian Goldby

8

@Udi'den kodu birden fazla sinyali (fantezi bir şey) desteklemek için uyarladım:

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

Bu kod, klavye kesme çağrısını ( SIGINT) ve SIGTERM( kill <process>) destekler


5

Matt J'nin cevabının aksine , basit bir nesne kullanıyorum. Bu bana bu işleyiciyi güvenli durması gereken tüm iş parçacıklarına ayrıştırma imkanı verir.

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)

başka yerde

while True:
    # task
    if handler.SIGINT:
        break

Bir olay kullanmalı veya time.sleep()bir değişken üzerinde meşgul döngüsü yapmak yerine.
OlivierM

@OlivierM Bu gerçekten uygulamaya özgüdür ve kesinlikle bu örneğin amacı değildir. Örneğin, aramaları engellemek veya işlevleri beklemek CPU'yu meşgul etmez. Dahası, bu sadece işlerin nasıl yapılabileceğinin bir örneğidir. Klavye Kesintiler diğer yanıtlarda belirtildiği gibi genellikle yeterlidir.
Thomas Devoogdt

4

Python'da sinyal işleyicileri ayarlamak için Python'un yerleşik sinyal modülündeki işlevleri kullanabilirsiniz . Özel olarak, signal.signal(signalnum, handler)işlev handlersinyal işlevini kaydetmek için kullanılır signalnum.


3

mevcut cevaplar için teşekkürler, ancak eklendi 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

Temizleme işleminizin tamamlandığından emin olmak istiyorsanız, SIG_IGN kullanarak Matt J'nin cevabına ekleyeceğim, böylece daha fazla SIGINTgöz ardı edilir, böylece temizleme kesintiye uğramaz .

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

Şahsen, engelleme olan standart soket (IPC) modunu kullandığım için KeyboardInterrupt'ı denemedim / kullanamadım. Böylece SIGINT işaretlendi, ancak sadece sokete veri alındıktan sonra geldi.

Bir sinyal işleyici ayarlamak aynı şekilde davranır.

Öte yandan, bu sadece gerçek bir terminal için çalışır. Diğer başlangıç ortamlar kabul etmeyebilir Ctrl+ Cveya sinyal ön kolu.

Ayrıca, Python'da, yorumlayıcının kendisinden temiz çıkması gerektiğinden farklı olan "İstisnalar" ve "BaseExceptions" vardır, bu nedenle bazı istisnalar diğerlerinden daha yüksek önceliğe sahiptir (İstisnalar BaseException'dan türetilmiştir)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.