2009-05-09 13 views
39

Piszę program, który buforuje niektóre wyniki przez moduł pikle. Co się dzieje w tej chwili jest to, że jeśli uderzę CTRL-C podczas operacji dump występuje, dump zostaje przerwany i wynikowy plik jest uszkodzony (czyli tylko częściowo napisany, więc to nie może być load ed ponownie.Jak zapobiec blokowaniu kodu przez KeyboardInterrupt w Pythonie?

Czy istnieje sposób, aby dump, albo w ogóle bloku kodu, żródło Moje bieżące obejście wygląda mniej więcej tak:?

try: 
    file = open(path, 'w') 
    dump(obj, file) 
    file.close() 
except KeyboardInterrupt: 
    file.close() 
    file.open(path,'w') 
    dump(obj, file) 
    file.close() 
    raise 

wydaje się głupie, aby ponownie uruchomić operację, jeśli zostanie przerwane, więc szukam sposobu przerwać przerwanie Jak to zrobić?

Odpowiedz

31

Umieść funkcję w wątku i poczekaj na zakończenie wątku.

Nici w języku Python nie mogą być przerywane, z wyjątkiem specjalnej aplikacji C api.

import time 
from threading import Thread 

def noInterrupt(): 
    for i in xrange(4): 
     print i 
     time.sleep(1) 

a = Thread(target=noInterrupt) 
a.start() 
a.join() 
print "done" 


0 
1 
2 
3 
Traceback (most recent call last): 
    File "C:\Users\Admin\Desktop\test.py", line 11, in <module> 
    a.join() 
    File "C:\Python26\lib\threading.py", line 634, in join 
    self.__block.wait() 
    File "C:\Python26\lib\threading.py", line 237, in wait 
    waiter.acquire() 
KeyboardInterrupt 

Zobacz, jak przerwanie zostało odroczone do czasu zakończenia wątku?

Tutaj jest przystosowany do użytku:

import time 
from threading import Thread 

def noInterrupt(path, obj): 
    try: 
     file = open(path, 'w') 
     dump(obj, file) 
    finally: 
     file.close() 

a = Thread(target=noInterrupt, args=(path,obj)) 
a.start() 
a.join() 
+0

Super pomocne, dzięki. – JeffThompson

+1

To rozwiązanie jest lepsze niż te z modułem 'signal', ponieważ znacznie łatwiej jest go uzyskać. Nie jestem pewien, czy możliwe jest nawet napisanie solidnego rozwiązania opartego na sygnale. – benrg

22

użyć modułu signal wyłączyć SIGINT na czas trwania procesu:

s = signal.signal(signal.SIGINT, signal.SIG_IGN) 
do_important_stuff() 
signal.signal(signal.SIGINT, s) 
+1

Chciałem to zasugerować, ale to nie działa w Windows. – Unknown

+0

Poszłabym na to również, gdyby był w systemie podobnym do Uniksa. –

+2

To działa w systemie Windows. Dzieje się tak poprzez emulację sygnałów Posix za pośrednictwem biblioteki wykonawczej C https://msdn.microsoft.com/en-us/library/xdkz3x12%28v=vs.90%29.aspx –

8

Moim zdaniem przy użyciu wątki na to jest przesada . Możesz upewnić się, że plik jest zapisany poprawnie, po prostu robi to w pętli aż udany zapisu zrobiono:

def saveToFile(obj, filename): 
    file = open(filename, 'w') 
    cPickle.dump(obj, file) 
    file.close() 
    return True 

done = False 
while not done: 
    try: 
     done = saveToFile(obj, 'file') 
    except KeyboardInterrupt: 
     print 'retry' 
     continue 
+1

+1: To podejście jest znacznie bardziej pythonic i łatwiejsze do zrozumienia niż pozostałe dwie. – kquinn

+1

+ - 0: To podejście nie jest tak dobre, ponieważ można je przerwać na zawsze, przytrzymując klawisz crtl + c, podczas gdy moja metoda wątku nigdy nie zostanie przerwana. Zauważ także, że musisz mieć kolejną zmienną "przerwaną" i inną instrukcję warunku, aby ją później powtórzyć. – Unknown

+2

Podejście to również restartuje zrzut za każdym razem, co jest częścią tego, czego chciałem uniknąć. – saffsd

46

Poniżej znajduje się kierownik kontekst że przywiązuje obsługi sygnału dla SIGINT. Jeśli wywoływana jest obsługa sygnału menedżera kontekstu, sygnał jest opóźniany przez przekazanie sygnału do oryginalnej procedury obsługi tylko po wyjściu menedżera kontekstów.

import signal 
import logging 

class DelayedKeyboardInterrupt(object): 
    def __enter__(self): 
     self.signal_received = False 
     self.old_handler = signal.signal(signal.SIGINT, self.handler) 

    def handler(self, sig, frame): 
     self.signal_received = (sig, frame) 
     logging.debug('SIGINT received. Delaying KeyboardInterrupt.') 

    def __exit__(self, type, value, traceback): 
     signal.signal(signal.SIGINT, self.old_handler) 
     if self.signal_received: 
      self.old_handler(*self.signal_received) 

with DelayedKeyboardInterrupt(): 
    # stuff here will not be interrupted by SIGINT 
    critical_code() 
+7

Mimo że na początku może wydawać się to zniechęcające, myślę, że jest to najczystsze i najbardziej nadające się do ponownego wykorzystania rozwiązanie. Ostatecznie definiujesz menedżera kontekstu tylko jeden raz (i możesz to łatwo zrobić w swoim module, jeśli chcesz), a potrzebujesz tylko jednej linii "z", gdziekolwiek chcesz z niej skorzystać, to duży plus dla czytelności twojego kodu. – blubberdiblub

+0

Dzięki za to. Dla każdego, kto testuje to rozwiązanie, nie próbuj go używać jednorazowo, zamiast go używać w miejsce critical_code. Wyjdzie natychmiast. (Może to typowe dla innych rozwiązań - nie jestem pewien) – Justin

+1

@Justin: to dlatego, że procedury obsługi sygnału mogą wystąpić jedynie pomiędzy "atomowymi" instrukcjami interpretera Pythona. (3 punkt z https://docs.python.org/library/signal.html) –

Powiązane problemy