如何防止Python中的KeyboardInterrupt中断代码块?

How to prevent a block of code from being interrupted by KeyboardInterrupt in Python?

我正在编写一个程序,通过pickle模块缓存一些结果。此时发生的情况是,如果我在执行dump操作时按下ctrl-c,dump会被中断,并导致文件损坏(即仅部分写入,因此无法再次执行loaded)。

有没有办法使dump或一般的代码块不间断?我当前的解决方案如下:

1
2
3
4
5
6
7
8
9
10
try:
  file = open(path, 'w')
  dump(obj, file)
  file.close()
except KeyboardInterrupt:
  file.close()
  file.open(path,'w')
  dump(obj, file)
  file.close()
  raise

如果操作被中断,重新启动它似乎很愚蠢,所以我正在寻找一种延迟中断的方法。我该怎么做?


下面是一个上下文管理器,它为SIGINT附加了一个信号处理程序。如果调用了上下文管理器的信号处理程序,则只有在上下文管理器退出时,才通过将信号传递给原始处理程序来延迟信号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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()


将函数放入线程中,等待线程完成。

不能中断Python线程,除非使用特殊的C API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

看看中断是如何延迟到线程结束的?

在这里,它适合您的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
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()


使用信号模块在处理期间禁用SIGINT:

1
2
3
s = signal.signal(signal.SIGINT, signal.SIG_IGN)
do_important_stuff()
signal.signal(signal.SIGINT, s)


在我看来,用线程来解决这个问题是一种过分的杀伤力。在成功写入之前,只需在循环中执行以下操作,即可确保正确保存文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
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


这个问题是关于阻止KeyboardInterrupt的,但是在这种情况下,我发现原子文件的写入更干净,并提供了额外的保护。

使用原子写入,要么整个文件被正确写入,要么什么也不做。StackOverflow有多种解决方案,但就我个人而言,我喜欢使用AtomicWrites库。

运行pip install atomicwrites后,按如下方式使用:

1
2
3
4
from atomicwrites import atomic_write

with atomic_write(path, overwrite=True) as file:
    dump(obj, file)