2009-05-09 153 views
39

我在写一个程序,通过pickle模块缓存一些结果。会发生什么,此刻是,如果我打在CTRL-C,而dump操作正在发生,dump被中断,导致文件被破坏(即只有部分写的,所以它不能再load如何防止一段代码被Python中的KeyboardInterrupt中断?

有一种方法,使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 

看来愚蠢,如果它被中断,重新启动的操作,所以我在寻找一种方式推迟中断。我该怎么做?

回答

31

将函数放在一个线程中,然后等待线程完成。

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() 
+0

超级有用,谢谢。 – JeffThompson 2015-11-25 15:39:34

+1

该解决方案比涉及'signal'模块的解决方案更好,因为它更容易正确。我不确定甚至有可能编写一个强大的基于'signal'的解决方案。 – benrg 2017-11-09 09:17:13

22

使用signal模块禁用SIGINT为过程的持续时间:

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

我打算提出这个建议,但它不适用于Windows。 – Unknown 2009-05-09 06:52:37

+0

如果是像unix这样的系统,我也会为此而去 – 2009-05-09 11:50:40

+2

这可以在windows上运行。它通过仿真通过C运行时库的Posix信号发生https://msdn.microsoft.com/en-us/library/xdkz3x12%28v=vs.90%29.aspx – 2015-04-24 08:17:18

8

在使用线程从此我的看法是矫枉过正。您可以确保文件被正确保存,只需做一个循环,直到成功写做:

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:这种方法更加pythonic比其他两个更容易理解。 – kquinn 2009-05-09 11:56:17

+1

+ - 0:这种方法不是很好,因为您可以通过按住crtl + c来永久中断它,而我的线程方法永远不会中断。还要注意,你必须有另一个变量“isinterrupted”和另一个条件语句来重新评估它之后。 – Unknown 2009-05-09 18:14:54

+2

这种方法每次都会重新启动转储,这是我想避免的一部分。 – saffsd 2009-05-10 01:23:25

46

以下是上下文管理,重视对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() 
+7

尽管初看起来有些让人望而生畏,但我认为这是最干净和最可重用的解决方案。 毕竟,你只定义了一次上下文管理器(如果你喜欢,你可以在它自己的模块中轻松完成),然后你只需要一个''和''行,无论你想使用它,哪个对于代码的可读性来说是一大优势。 – blubberdiblub 2014-04-17 03:59:35

+0

谢谢你。对于测试此解决方案的任何人,请不要只用一次time.sleep调用来代替critical_code。它将立即退出。 (也许这是其他解决方案的典型特征 - 我不确定) – Justin 2016-02-02 16:29:52

+1

@Justin:那是因为信号处理程序只能在Python解释器的“原子”指令之间发生。 (来自https://docs.python.org/library/signal.html的第三点) – 2016-02-10 12:47:00