Как предотвратить блокирование блока кода с помощью KeyboardInterrupt в Python?
Я пишу программу, которая кэширует некоторые результаты через модуль pickle. То, что происходит на данный момент, заключается в том, что если я нажму ctrl-c во время выполнения операции dump
, dump
будет прерван, а результирующий файл будет поврежден (т.е. Только частично написан, поэтому он не может быть load
ed снова.
Есть ли способ сделать dump
, или вообще блок кода, бесперебойным? Мое текущее обходное решение выглядит примерно так:
try:
file = open(path, 'w')
dump(obj, file)
file.close()
except KeyboardInterrupt:
file.close()
file.open(path,'w')
dump(obj, file)
file.close()
raise
Кажется глупым перезапустить операцию, если она прерывается, поэтому я ищу способ отложить прерывание. Как это сделать?
Ответы
Ответ 1
Поместите функцию в поток и дождитесь завершения потока.
Нити Python не могут быть прерваны, кроме специального 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
Посмотрите, как прерывание было отложено до завершения потока?
Здесь он адаптирован к вашему использованию:
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()
Ответ 2
Ниже представлен менеджер контекста, который прикрепляет обработчик сигнала для SIGINT
. Если вызывается обработчик сигнала диспетчера контекста, сигнал задерживается, только передавая сигнал исходному обработчику, когда вызывается менеджер контекста.
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()
Ответ 3
Используйте модуль signal, чтобы отключить SIGINT в течение всего процесса:
s = signal.signal(signal.SIGINT, signal.SIG_IGN)
do_important_stuff()
signal.signal(signal.SIGINT, s)
Ответ 4
По-моему, использование потоков для этого - перебор. Вы можете убедиться, что файл сохранен правильно, просто выполнив его в цикле до тех пор, пока не будет выполнена успешная запись:
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