在Python中,如何防止代码块被键盘中断中断?

在Python中,如何防止代码块被键盘中断中断?,python,Python,我正在编写一个程序,通过pickle模块缓存一些结果。此时发生的情况是,如果在执行dump操作时按ctrl-c,则dump会被中断,并且生成的文件已损坏(即,仅部分写入,因此无法再次加载 有没有办法使转储,或者通常是一块代码不间断?我目前的解决方法如下: try: file = open(path, 'w') dump(obj, file) file.close() except KeyboardInterrupt: file.close() file.open(path,'

我正在编写一个程序,通过pickle模块缓存一些结果。此时发生的情况是,如果在执行
dump
操作时按ctrl-c,则
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

如果操作被中断,重新启动操作似乎很愚蠢,因此我正在寻找一种推迟中断的方法。我该如何做?

将函数放入线程,然后等待线程完成

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
使用模块在进程期间禁用SIGINT:

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

在我看来,使用线程进行此操作是一种过分的做法。您只需在循环中执行此操作,直到成功完成写入操作,即可确保文件正确保存:

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

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

import signal
import logging

class DelayedKeyboardInterrupt:

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

这个问题是关于阻止
键盘中断
,但是对于这种情况,我发现原子文件写入更干净,并提供了额外的保护

对于原子写入,要么整个文件被正确写入,要么什么都不正确。Stackoverflow有一个函数库,但就我个人而言,我喜欢使用库

运行
pip install atomicwrites
后,只需像这样使用它:


一种通用方法是使用上下文管理器,该管理器接受一组要挂起的信号:

import signal

from contextlib import contextmanager


@contextmanager
def suspended_signals(*signals):
    """
    Suspends signal handling execution
    """
    signal.pthread_sigmask(signal.SIG_BLOCK, set(signals))
    try:
        yield None
    finally:
        signal.pthread_sigmask(signal.SIG_UNBLOCK, set(signals))


如果是在类似unix的系统中,我也会这么做。这在windows上确实有效。它通过C运行时库模拟Posix信号来实现,比线程简单得多,这是一个很好的解决方案。谢谢。如果在执行
do\u important\u stuff()过程中出现信号
,信号一旦不被忽略就会触发吗?我认为这在大多数情况下是最干净的解决方案。唯一的问题是当你不在主线程中工作时:“信号只在主线程中工作”.Hmpf.+1:此方法比其他两种方法更具python风格且更易于理解。+-0:此方法不太好,因为您可以通过按住crtl+c永远中断它,而我的线程方法永远不会被中断。另外请注意,您必须使用另一个变量“isinterrupted”和另一个条件语句,以在以后重新验证。这种方法每次都会重新启动转储,这是我想要避免的。@Unknown,@Saffsd:你们都是对的。但此解决方案适用于简单的应用程序,在这些应用程序中,您不希望被恶意使用。它是一种解决用户中断转储的非常不可能的事件的方法e在不知情的情况下转储。您可以选择最适合您的应用程序的解决方案。不,@Saffsd不对。他应该将dump()移出saveToFile()。然后调用dump()一次,然后调用saveToFile()需要多少次就有多少次。尽管一开始对某些人来说可能会让人望而生畏,但我认为这是最干净、最可重用的解决方案。毕竟,您只定义了一次上下文管理器(如果愿意,您可以在自己的模块中轻松地完成)然后,无论您想在哪里使用它,您都只需要一行“with”,这对代码的可读性是一个很大的好处。谢谢。对于任何测试此解决方案的人,不要只尝试一次。使用sleep call代替关键的_代码。它将立即退出。(可能这是其他解决方案的典型情况——我不确定)@贾斯汀:这是因为信号处理程序只能在Python解释器的“原子”指令之间出现@Perkins:但是这会从全局名称空间中隐藏
某个东西
。如果这对您来说是个问题,因为您有一个名为
信号
,那么最好的解决方案是将此代码放在它自己的模块中。非常好的类,谢谢。我扩展了它,一次支持多个信号-有时您还想对
信号做出反应M
除了
SIGINT
:这个解决方案比涉及
signal
模块的解决方案要好,因为它更容易正确实现。我甚至不确定是否有可能编写一个基于
signal
的健壮解决方案。好的,因此在python中中断线程后,线程似乎不会继续打印3-中断立即出现,但线程仍在后台运行。如果要从noInterrupt()函数返回某些内容,变量“a”会得到返回值吗?
from atomicwrites import atomic_write

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

from contextlib import contextmanager


@contextmanager
def suspended_signals(*signals):
    """
    Suspends signal handling execution
    """
    signal.pthread_sigmask(signal.SIG_BLOCK, set(signals))
    try:
        yield None
    finally:
        signal.pthread_sigmask(signal.SIG_UNBLOCK, set(signals))