我在Win7 64位上运行64位Python 2.7.3。通过执行以下操作,我可以可靠地使Python解释器崩溃:

>>> from scipy import stats
>>> import time
>>> time.sleep(3)

并在 sleep 期间按Control-C。没有引发KeyboardInterrupt;解释器崩溃。打印以下内容:
forrtl: error (200): program aborting due to control-C event
Image              PC                Routine            Line        Source

libifcoremd.dll    00000000045031F8  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044FC789  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044E8583  Unknown               Unknown  Unknown
libifcoremd.dll    000000000445725D  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044672A6  Unknown               Unknown  Unknown
kernel32.dll       0000000077B74AF3  Unknown               Unknown  Unknown
kernel32.dll       0000000077B3F56D  Unknown               Unknown  Unknown
ntdll.dll          0000000077C73281  Unknown               Unknown  Unknown

这使得不可能中断长时间运行的scipy计算。

搜寻“forrtl”之类的内容,我看到这样的问题的建议是由于使用了覆盖Ctrl-C处理的Fortran库。我看不到Scipy跟踪器上的错误,但是鉴于Scipy是供Python使用的库,我认为这是一个错误。它破坏了Python对Ctrl-C的处理。有什么解决方法吗?

编辑:按照@cgohlke的建议,我尝试在导入scipy之后添加自己的处理程序。关于相关问题的This question显示添加信号处理程序不起作用。我尝试通过pywin32使用Windows API SetConsoleCtrlHandler函数:
from scipy import stats
import win32api
def doSaneThing(sig, func=None):
    print "Here I am"
    raise KeyboardInterrupt
win32api.SetConsoleCtrlHandler(doSaneThing, 1)

此后,按Ctrl-C会打印“Here I am”,但Python仍然会因forrtl错误而崩溃。有时我还会收到一条消息,提示“ConsoleCtrlHandler函数失败”,该消息很快消失了。

如果我在IPython中运行此命令,则可以在forrtl错误之前看到正常的Python KeyboardInterrupt回溯。如果我引发其他错误而不是KeyboardInterrupt(例如ValueError),我还会看到正常的Python回溯,然后是forrtl错误:
ValueError                                Traceback (most recent call last)
<ipython-input-1-08defde66fcb> in doSaneThing(sig, func)
      3 def doSaneThing(sig, func=None):
      4     print "Here I am"
----> 5     raise ValueError
      6 win32api.SetConsoleCtrlHandler(doSaneThing, 1)

ValueError:
forrtl: error (200): program aborting due to control-C event
[etc.]

看来,无论底层处理程序在做什么,它不仅在直接捕获Ctrl-C,而且还在对错误情况(ValueError)使用react并使其自身崩溃。有什么办法可以消除这种情况?

最佳答案

这是您发布的解决方案的一种变体,可能会起作用。也许有更好的方法来解决此问题-甚至可以通过设置一个告诉DLL跳过安装处理程序的环境变量来避免所有这些情况。希望这会有所帮助,直到您找到更好的方法。

time module(第868-876行)和 _multiprocessing module(第312-321行)都称为 SetConsoleCtrlHandler 。对于time模块,其控制台控制处理程序将设置Windows事件hInterruptEvent。对于主线程,time.sleep通过WaitForSingleObject(hInterruptEvent, ul_millis)等待此事件,其中ul_millis是 sleep 的毫秒数,除非被Ctrl + C中断。由于您已安装的处理程序返回True,因此永远不会调用time模块的处理程序来设置hInterruptEvent,这意味着sleep无法被中断。

我尝试使用imp.init_builtin('time')重新初始化time模块,但显然SetConsoleCtrlHandler忽略了第二个调用。似乎必须删除该处理程序,然后将其重新插入。不幸的是,time模块没有为此导出函数。因此,作为麻烦,只需确保在安装处理程序后导入time模块即可。由于导入scipy也会导入time,因此您需要使用ctypes预加载libifcoremd.dll才能以正确的顺序获取处理程序。最后,添加对thread.interrupt_main的调用,以确保调用了Python的SIGINT处理程序[1]。

例如:

import os
import imp
import ctypes
import thread
import win32api

# Load the DLL manually to ensure its handler gets
# set before our handler.
basepath = imp.find_module('numpy')[1]
ctypes.CDLL(os.path.join(basepath, 'core', 'libmmd.dll'))
ctypes.CDLL(os.path.join(basepath, 'core', 'libifcoremd.dll'))

# Now set our handler for CTRL_C_EVENT. Other control event
# types will chain to the next handler.
def handler(dwCtrlType, hook_sigint=thread.interrupt_main):
    if dwCtrlType == 0: # CTRL_C_EVENT
        hook_sigint()
        return 1 # don't chain to the next handler
    return 0 # chain to the next handler

win32api.SetConsoleCtrlHandler(handler, 1)

>>> import time
>>> from scipy import stats
>>> time.sleep(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyboardInterrupt

[1] interrupt_main调用PyErr_SetInterrupt。这会触发Handlers[SIGINT]并调用Py_AddPendingCall以添加checksignals_witharg。依次调用PyErr_CheckSignals。由于Handlers[SIGINT]跳闸,因此调用Handlers[SIGINT].func。最后,如果funcsignal.default_int_handler,则将获得KeyboardInterrupt异常。

关于python - 导入scipy.stats后,Ctrl-C会使Python崩溃,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15457786/

10-13 07:41
查看更多