导入scipy.stats后,Ctrl-C崩溃了Python

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了导入scipy.stats后,Ctrl-C崩溃了Python相关的知识,希望对你有一定的参考价值。

我在Win7 64位上运行64位Python 2.7.3。我可以通过这样做可靠地崩溃Python解释器:

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

并在睡眠期间按下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打印“我在这里”,但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)做出反应并使自身崩溃。有没有办法消除这个?

答案

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

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

我尝试使用imp.init_builtin('time')重新初始化time模块,但显然SetConsoleCtrlHandler忽略了第二次调用。似乎必须删除处理程序然后重新插入。不幸的是,time模块没有为此导出函数。因此,作为一个kludge,只需确保在安装处理程序后导入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_mainPyErr_SetInterrupt。这次旅行Handlers[SIGINT]并呼吁Py_AddPendingCall添加checksignals_witharg。反过来,这称为PyErr_CheckSignals。由于Handlers[SIGINT]被绊倒,这称为Handlers[SIGINT].func。最后,如果funcsignal.default_int_handler,你会得到一个KeyboardInterrupt例外。

另一答案

通过这样做我已经能够得到一个半解决方法:

from scipy import stats
import win32api
def doSaneThing(sig, func=None):
    return True
win32api.SetConsoleCtrlHandler(doSaneThing, 1)

在处理程序中返回true会停止处理程序链,以便不再调用插入的Fortran处理程序。但是,这种解决方法只是部分解决,原因有两个:

  1. 它实际上并没有引发KeyboardInterrupt,这意味着我无法在Python代码中对它做出反应。它只是让我回到提示。
  2. 它不像Ctrl-C通常在Python中那样完全中断。如果在新的Python会话中我执行time.sleep(3)并按Ctrl-C,则会立即中止睡眠并获得KeyboardInterrupt。通过上述解决方法,睡眠不会中止,并且只有在睡眠时间结束后控制才会返回到提示。

尽管如此,这仍然比崩溃整个会议更好。对我来说,这引发了一个问题:为什么SciPy(以及任何其他依赖这些英特尔库的Python库)本身不会这样做。

我不接受这个答案,希望有人可以提供真正的解决方案或解决方法。 “真实”是指在长时间运行的SciPy计算过程中按Ctrl-C应该像没有加载SciPy时那样工作。 (请注意,这并不意味着它必须立即工作。非SciPy计算如普通Python sum(xrange(100000000))可能不会立即在Ctrl-C上中止,但至少在它们这样做时,它们会引发KeyboardInterrupt。)

另一答案

这是修补dll以删除安装Ctrl-C处理程序的调用的代码:

import os
import os.path
import imp
import hashlib

basepath = imp.find_module('numpy')[1]
ifcoremd = os.path.join(basepath, 'core', 'libifcoremd.dll')
with open(ifcoremd, 'rb') as dll:
    contents = dll.read()

m = hashlib.md5()
m.update(contents)

patch = {'7cae928b035bbdb90e4bfa725da59188': (0x317FC, 'xebx0b'),
  '0f86dcd44a1c2e217054c50262f727bf': (0x3fdd9, 'xebx10')}[m.hexdigest()]
if patch:
    contents = bytearray(contents)
    contents[patch[0]:patch[0] + len(patch[1])] = patch[1]
    with open(ifcoremd, 'wb') as dll:
        dll.write(contents)
else:
    print 'Unknown dll version'

编辑:这是我为x64添加补丁的方法。在调试器中运行python.exe,并为SetConsoleCtrlHandler设置断点,直到您要进行要修补的调用:

Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: .venvScriptspython.exe
...
0:000> .symfix
0:000> bp kernel32!SetConsoleCtrlHandler
0:000> g
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
Child-SP          RetAddr           Call Site
00000000`007ef7a8 00000000`71415bb4 KERNEL32!SetConsoleCtrlHandler
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:WINDOWSSYSTEM32python27.dll -
00000000`007ef7b0 00000000`7035779f MSVCR90!signal+0x17c
00000000`007ef800 00000000`70237ea7 python27!PyOS_getsig+0x3f
00000000`007ef830 00000000`703546cc python27!Py_Main+0x21ce7
00000000`007ef880 00000000`7021698c python27!Py_InitializeEx+0x40c
0:000> g
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec  5 2015, 20:40:30) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
...
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
Child-SP          RetAddr           Call Site
00000000`007ec308 00000000`7023df6e KERNEL32!SetConsoleCtrlHandler
00000000`007ec310 00000000`70337877 python27!PyTime_DoubleToTimet+0x10ee
00000000`007ec350 00000000`7033766d python27!PyImport_IsScript+0x4f7
00000000`007ec380 00000000`70338bf2 python27!PyImport_IsScript+0x2ed
00000000`007ec3b0 00000000`703385a9 python27!PyImport_ImportModuleLevel+0xc82
0:000> g
...
>>> import scipy.stats
...
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:UserskevinDocuments\venvlibsite-packages
umpycorelibifcoremd.dll -
Child-SP          RetAddr           Call Site
00000000`007ed818 00007ffc`828309eb KERNEL32!SetConsoleCtrlHandler
00000000`007ed820 00007ffc`828dfa44 libifcoremd!GETEXCEPTIONPTRSQQ+0xdb
00000000`007ed880 00007ffc`828e59d7 libifcoremd!for_lt_ne+0xc274
00000000`007ed8b0 00007ffc`828e5aff libifcoremd!for_lt_ne+0x12207
00000000`007ed8e0 00007ffc`c292ddc7 libifcoremd!for_lt_ne+0x1232f
0:000> ub  00007ffc`828309eb
libifcoremd!GETEXCEPTIONPTRSQQ+0xbb:
00007ffc`828309cb 00e8            add     al,ch
00007ffc`828309cd df040b          fild    word ptr [rbx+rcx]
00007ffc`828309d0 0033            add     byte ptr [rbx],dh
00007ffc`828309d2 c9              leave
00007ffc`828309d3 ff15bf390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40bc8 (00007ffc`82914398)]
00007ffc`828309d9 488d0d00efffff  lea     rcx,[libifcoremd!for_rtl_finish_+0x20 (00007ffc`8282f8e0)]
00007ffc`828309e0 ba01000000      mov     edx,1
00007ffc`828309e5 ff158d390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`82914378)]

我们将用相对的lea(这是jmp后跟要跳转的字节数)修补0xeb指令

0:000> ? 00007ffc`828309eb - 00007ffc`828309d9
Evaluate expression: 18 = 00000000`00000012
0:000> f 00007ffc`828309d9 L2 eb 10
Filled 0x2 bytes
0:000> ub  00007ffc`828309eb
libifcoremd!GETEXCEPTIONPTRSQQ+0xbe:
00007ffc`828309ce 040b            add     al,0Bh
00007ff

以上是关于导入scipy.stats后,Ctrl-C崩溃了Python的主要内容,如果未能解决你的问题,请参考以下文章

scipy.stats 转换器正常 z 分数到 p 值 Python3

部署后在 django 中使用 scipy.stats.stats

scipy.stats.anderson 测试临界值

调用 scipy.stats.multivariate_normal 后,pylab.plot“无法将浮点 NaN 转换为整数”

如何用python计算临界值(critical value)和p值(p value)(scipy)

在 scipy 中将 qmc 作为子模块导入