2009-05-09 14 views
39

Sto scrivendo un programma che memorizza nella cache alcuni risultati tramite il modulo pickle. Quello che succede in questo momento è che se ho colpito ctrl-c quando l'operazione dump si sta verificando, dump viene interrotto e il file risultante è corrotto (cioè solo parzialmente scritto, quindi non può essere di nuovo load ed.Come impedire che un blocco di codice venga interrotto da KeyboardInterrupt in Python?

C'è un modo per rendere dump, o, in generale, un blocco di codice, uninterruptable mia soluzione attuale simile a questa:?

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

sembra stupido per riavviare l'operazione se si è interrotto, quindi sono alla ricerca di un modo per posticipare l'interrupt Come posso fare?

risposta

31

Inserisci la funzione in una discussione e attendi che il thread termini.

I thread Python non possono essere interrotti se non con uno speciale 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 

Vedere come l'interruzione è stata posticipata fino al termine del thread?

Qui è adattato a vostro uso:

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 utile, grazie. – JeffThompson

+1

Questa soluzione è migliore di quella che coinvolge il modulo 'signal' perché è molto più facile farlo bene. Non sono sicuro che sia persino possibile scrivere una robusta soluzione basata sul segnale. – benrg

22

Utilizza il modulo signal per disabilitare SIGINT per tutta la durata del processo:

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

Stavo per suggerire questo, ma non funziona in Windows. – Unknown

+0

Sarei andato anche per quello se fosse in un sistema simile a unix –

+2

Questo funziona su Windows. Accade tramite l'emulazione dei segnali Posix tramite la libreria di runtime C https://msdn.microsoft.com/en-us/library/xdkz3x12%28v=vs.90%29.aspx –

8

A mio parere utilizzando thread per questo è un peso inutile . È possibile assicurarsi che il file sia salvato correttamente semplicemente farlo in un ciclo fino a quando una scrittura di successo è stato fatto:

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: questo approccio è molto più pitone e più facile da capire rispetto agli altri due. – kquinn

+1

+ - 0: Questo approccio non è buono perché è possibile interromperlo per sempre tenendo premuto crtl + c mentre il mio approccio al thread non viene mai interrotto. Si noti inoltre che è necessario avere un'altra variabile "interrotta" e un'altra istruzione di condizione per controrilanciare successivamente. – Unknown

+2

Questo approccio riavvia anche il dump ogni volta, che è parte di ciò che volevo evitare. – saffsd

46

Quello che segue è un gestore di contesto che attribuisce un gestore di segnale per SIGINT. Se viene chiamato il gestore di segnale del gestore di contesto, il segnale viene ritardato passando solo il segnale al gestore originale quando si chiude il gestore di contesto.

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

Anche se all'inizio potrebbe sembrare scoraggiante, penso che questa sia la soluzione più pulita e riutilizzabile. Dopotutto, definisci il gestore del contesto solo una volta (e puoi farlo facilmente nel suo modulo, se preferisci) e poi hai solo bisogno di una linea '' con '' ovunque tu voglia utilizzarla, che è un grande vantaggio per la leggibilità del tuo codice. – blubberdiblub

+0

Grazie per questo. Per chiunque provi questa soluzione, non provarla con una sola chiamata time.sleep al posto di critical_code. Uscirà immediatamente. (Forse questo è tipico di altre soluzioni - Non sono sicuro) – Justin

+1

@Justin: questo perché i gestori di segnale possono verificarsi solo tra le istruzioni "atomiche" dell'interprete Python. (3 ° punto da https://docs.python.org/library/signal.html) –

Problemi correlati