我正在编写一个程序,通过pickle模块缓存一些结果.此刻发生的情况是,如果我在dump操作发生时按下ctrl-c,则会dump中断并且生成的文件已损坏(即只是部分写入,因此无法load再次编辑.
有没有办法制作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
Run Code Online (Sandbox Code Playgroud)
如果它被中断,重启操作似乎很愚蠢,所以我正在寻找一种推迟中断的方法.我该怎么做呢?
Gar*_*rwe 60
以下是附加信号处理程序的上下文管理器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()
Run Code Online (Sandbox Code Playgroud)
Unk*_*own 41
将函数放在一个线程中,等待线程完成.
除了特殊的C api之外,不能中断Python线程.
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
Run Code Online (Sandbox Code Playgroud)
看看在线程完成之前中断是如何推迟的?
在这里它适合您的使用:
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()
Run Code Online (Sandbox Code Playgroud)
Ign*_*ams 25
使用信号模块在进程持续时间内禁用SIGINT:
s = signal.signal(signal.SIGINT, signal.SIG_IGN)
do_important_stuff()
signal.signal(signal.SIGINT, s)
Run Code Online (Sandbox Code Playgroud)
Nad*_*mli 10
在我看来,使用线程是一种矫枉过正.您只需在循环中执行此操作即可确保正确保存文件,直到成功完成写入为止:
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
Run Code Online (Sandbox Code Playgroud)
我一直在思考对这个问题的答案的批评,我相信我已经实现了一个更好的解决方案,其使用方式如下:
with signal_fence(signal.SIGINT):
file = open(path, 'w')
dump(obj, file)
file.close()
Run Code Online (Sandbox Code Playgroud)
下面是上下文管理signal_fence器,随后解释了它对之前答案的改进。该函数的文档字符串记录了其接口和保证。
import os
import signal
from contextlib import contextmanager
from types import FrameType
from typing import Callable, Iterator, Optional, Tuple
from typing_extensions import assert_never
@contextmanager
def signal_fence(
signum: signal.Signals,
*,
on_deferred_signal: Callable[[int, Optional[FrameType]], None] = None,
) -> Iterator[None]:
"""
A `signal_fence` creates an uninterruptible "fence" around a block of code. The
fence defers a specific signal received inside of the fence until the fence is
destroyed, at which point the original signal handler is called with the deferred
signal. Multiple deferred signals will result in a single call to the original
handler. An optional callback `on_deferred_signal` may be specified which will be
called each time a signal is handled while the fence is active, and can be used
to print a message or record the signal.
A `signal_fence` guarantees the following with regards to exception-safety:
1. If an exception occurs prior to creating the fence (installing a custom signal
handler), the exception will bubble up as normal. The code inside of the fence will
not run.
2. If an exception occurs after creating the fence, including in the fenced code,
the original signal handler will always be restored before the exception bubbles up.
3. If an exception occurs while the fence is calling the original signal handler on
destruction, the original handler may not be called, but the original handler will
be restored. The exception will bubble up and can be detected by calling code.
4. If an exception occurs while the fence is restoring the original signal handler
(exceedingly rare), the original signal handler will be restored regardless.
5. No guarantees about the fence's behavior are made if exceptions occur while
exceptions are being handled.
A `signal_fence` can only be used on the main thread, or else a `ValueError` will
raise when entering the fence.
"""
handled: Optional[Tuple[int, Optional[FrameType]]] = None
def handler(signum: int, frame: Optional[FrameType]) -> None:
nonlocal handled
if handled is None:
handled = (signum, frame)
if on_deferred_signal is not None:
try:
on_deferred_signal(signum, frame)
except:
pass
# https://docs.python.org/3/library/signal.html#signal.getsignal
original_handler = signal.getsignal(signum)
if original_handler is None:
raise TypeError(
"signal_fence cannot be used with signal handlers that were not installed"
" from Python"
)
if isinstance(original_handler, int) and not isinstance(
original_handler, signal.Handlers
):
raise NotImplementedError(
"Your Python interpreter's signal module is using raw integers to"
" represent SIG_IGN and SIG_DFL, which shouldn't be possible!"
)
# N.B. to best guarantee the original handler is restored, the @contextmanager
# decorator is used rather than a class with __enter__/__exit__ methods so
# that the installation of the new handler can be done inside of a try block,
# whereas per [PEP 343](https://www.python.org/dev/peps/pep-0343/) the
# __enter__ call is not guaranteed to have a corresponding __exit__ call if an
# exception interleaves
try:
try:
signal.signal(signum, handler)
yield
finally:
if handled is not None:
if isinstance(original_handler, signal.Handlers):
if original_handler is signal.Handlers.SIG_IGN:
pass
elif original_handler is signal.Handlers.SIG_DFL:
signal.signal(signum, signal.SIG_DFL)
os.kill(os.getpid(), signum)
else:
assert_never(original_handler)
elif callable(original_handler):
original_handler(*handled)
else:
assert_never(original_handler)
signal.signal(signum, original_handler)
except:
signal.signal(signum, original_handler)
raise
Run Code Online (Sandbox Code Playgroud)
首先,为什么不使用线程(接受的答案)?
在非守护线程中运行代码确实可以保证该线程将在解释器关闭时加入,但主线程上的任何异常(例如KeyboardInterrupt)都不会阻止主线程继续执行。
考虑一下如果线程方法正在使用主线程finally在KeyboardInterrupt.
其次,使用上下文管理器解决@benrg对获得最多支持的答案的反馈:
- 如果在调用信号之后但在
__enter__返回之前引发异常,则该信号将被永久阻塞;
我的解决方案通过在@contextmanager装饰器的帮助下使用生成器上下文管理器来避免此错误。有关更多详细信息,请参阅上面代码中的完整注释。
- 此代码可能会在主线程以外的线程中调用第三方异常处理程序,而 CPython 绝不会这样做;
我认为这个错误不是真的。signal.signal需要从主线程调用,ValueError否则引发。这些上下文管理器只能在主线程上运行,因此只会从主线程调用第三方异常处理程序。
- 如果信号返回不可调用的值,
__exit__将会崩溃
我的解决方案处理信号处理程序的所有可能值并适当地调用它们。此外,我还assert_never受益于静态分析器中的详尽检查。
请注意,它signal_fence旨在处理主线程上的一个中断,例如KeyboardInterrupt. 如果您的用户在恢复信号处理程序时正在发送垃圾邮件ctrl,c那么没有什么可以拯救您。鉴于恢复处理程序所需执行的操作码相对较少,这种情况不太可能发生,但这是可能的。(为了获得最大的鲁棒性,该解决方案需要用C 重写)
| 归档时间: |
|
| 查看次数: |
16872 次 |
| 最近记录: |