百度移动端网站营销型网站策划
我正在编写一个程序,通过pickle模块缓存一些结果。此时发生的情况是,如果我在执行dump操作时按下ctrl-c,dump会被中断,并导致文件损坏(即仅部分写入,因此无法再次执行loaded)。
有没有办法使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
如果操作被中断,重新启动它似乎很愚蠢,所以我正在寻找一种延迟中断的方法。我该怎么做?
下面是一个上下文管理器,它为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()
尽管一开始对某些人来说可能令人望而生畏,但我认为这是最干净和最可重用的解决方案。毕竟,您只定义了一次上下文管理器(如果您愿意,您可以在自己的模块中轻松地这样做),然后您只需要在任何您想使用它的地方使用一行"with",这对于代码的可读性是一个很大的好处。
谢谢。对于任何测试这个解决方案的人来说,不要只是用一个时间来尝试它。睡眠调用代替关键的代码。它会立刻退出。(也许这是其他解决方案的典型——我不确定)
@贾斯汀:这是因为信号处理程序只能出现在Python解释器的"原子"指令之间。(第三点来自docs.python.org/library/signal.html)
您是全局命名空间中的handler方法shadows signal。应执行import signal as something或重命名处理程序内的signal变量。
@帕金斯:但这将使something从全局名称空间中消失。如果这对您来说是一个问题,因为您有一个名为signal的全局代码,那么最好的解决方案是将此代码放入它自己的模块中。
只有在其他地方使用something时。从机械上讲,它工作得很好,就像隐藏list一样,工作得很好,只是代码的可读性稍差。如果以前不清楚,我说的是DelayedKeyboardInterrupt.handler中的局部变量signal,而不是signal模块。典型的signal的缩写是sig,它不隐藏任何东西。
很好,谢谢。我把它扩展到一次支持多个信号——有时除了SIGINT之外,你还想对SIGTERM做出反应:gist.github.com/tcwalther/ae058c64d5d9078a9f33913718bba95
@Thomaswalther:单元测试+1。
您不必调用signal.getsignal()--signal.signal()返回旧的处理程序。
这个代码有问题,不要使用它。可能是非异常的bug列表:1。如果在调用signal之后,但在__enter__返回之前引发异常,则信号将永久阻塞;2.此代码可能会在主线程以外的线程中调用第三方异常处理程序,而cpython从未这样做;3.如果signal返回不可调用的值,__exit__将崩溃。@Thomaswalther的版本部分修复了bug 3,但至少添加了一个新bug。在gist上有许多类似的类;都至少有bug 1。我建议不要试图修复它们,这样做太难了。
@班格,哇,这是一种相当失败的态度。您所描述的bug只会在非常隐蔽的情况下遇到,这种情况很容易避免。仅仅因为这可能不适用于所有情况,并不意味着它不适用于所有情况。我真的认为你的评论是不具建设性的。
将函数放入线程中,等待线程完成。
不能中断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
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()
非常有用,谢谢。
这个解决方案比涉及signal模块的解决方案更好,因为它更容易得到正确的解决方案。我不确定是否有可能写一个健壮的基于signal的解决方案。
使用信号模块在处理期间禁用SIGINT:
s = signal.signal(signal.SIGINT, signal.SIG_IGN)
do_important_stuff()
signal.signal(signal.SIGINT, s)
我本来打算建议这样做,但它在Windows中不起作用。
如果它是在一个类Unix的系统中,我也会这么做的。
这在Windows上有效。它通过C运行库msdn.microsoft.com/en-us/library/xdkz3x12%28v=vs.90%29.aspx仿真POSIX信号发生。
在我看来,用线程来解决这个问题是一种过分的杀伤力。在成功写入之前,只需在循环中执行以下操作,即可确保正确保存文件:
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:这种方法比其他两种方法更容易理解,也更容易理解。
+-0:这种方法没有那么好,因为您可以通过按住crtl+c来永久中断它,而我的线程方法永远不会被中断。还要注意,您必须有另一个变量"isInterrupted"和另一个条件语句,以便在以后重新调用它。
这种方法每次都会重新启动转储,这是我想要避免的一部分。
@未知,@saffsd:你们都是对的。但是这个解决方案是为简单的应用程序设计的,在这些应用程序中您不需要恶意使用。对于用户在不知情的情况下中断转储的可能性非常小的事件,这是一种解决方法。您可以选择最适合您的应用程序的解决方案。
不,@saffsd不对。他应该将dump()移出saveToFile()。然后根据需要调用dump()和savetofile()。
实际上禁用信号对我来说似乎是最自然的方式。在这种情况下,我们不抑制它们,我们只是重复动作直到我们的信号不被抑制(如果幸运的话)。这几乎和问题的原著作者写的一模一样。这就是为什么我反对这个答案的原因。
这个问题是关于阻止KeyboardInterrupt的,但是在这种情况下,我发现原子文件的写入更干净,并提供了额外的保护。
使用原子写入,要么整个文件被正确写入,要么什么也不做。StackOverflow有多种解决方案,但就我个人而言,我喜欢使用AtomicWrites库。
运行pip install atomicwrites后,按如下方式使用:
from atomicwrites import atomic_write
with atomic_write(path, overwrite=True) as file:
dump(obj, file)