以下代码导致引发分段错误。我不确定为什么......
import numpy as np
from intbitset import intbitset
arr = np.array([1,2,3,4,5])
# This works
intbitset(arr.tolist())
=> intbitset([1, 2, 3, 4, 5])
# This throws SIGSEGV
intbitset([x for x in arr])
[x for x in arr]
完美运作并按预期返回列表。
有没有人对此有解释?在输入intbitset
ctr之前,列表推导不会被评估到列表中吗?
我已经在Python 3.6.3和2.7.13(需要将zip
更改为itertools.izip
)上进行了测试。两者都崩溃了。 intbitset
版本是2.3.0
这里有一些有趣的东西:
$ gdb python
...
(gdb) run crash.py
...
Thread 1 "python" received signal SIGSEGV, Segmentation fault.
0x00007ffff745338b in ?? () from /usr/lib/libpython3.6m.so.1.0
(gdb) bt
#0 0x00007ffff745338b in ?? ()
from /usr/lib/libpython3.6m.so.1.0
#1 0x00007ffff74a245f in ?? ()
from /usr/lib/libpython3.6m.so.1.0
#2 0x00007ffff73f0565 in PyList_Append ()
from /usr/lib/libpython3.6m.so.1.0
#3 0x00007ffff73a2580 in ?? ()
from /usr/lib/libpython3.6m.so.1.0
#4 0x00007ffff7404b55 in _PyCFunction_FastCallDict ()
from /usr/lib/libpython3.6m.so.1.0
#5 0x00007ffff740e10f in _PyObject_FastCallDict ()
from /usr/lib/libpython3.6m.so.1.0
#6 0x00007ffff73fc9d0 in PyFile_WriteObject ()
from /usr/lib/libpython3.6m.so.1.0
#7 0x00007ffff74a3d6a in PyFile_WriteString ()
from /usr/lib/libpython3.6m.so.1.0
#8 0x00007ffff74b2f9d in PyTraceBack_Print ()
from /usr/lib/libpython3.6m.so.1.0
#9 0x00007ffff7491154 in ?? ()
from /usr/lib/libpython3.6m.so.1.0
#10 0x00007ffff7327d14 in ?? ()
from /usr/lib/libpython3.6m.so.1.0
#11 0x00007ffff749136e in PyErr_Display ()
from /usr/lib/libpython3.6m.so.1.0
#12 0x00007ffff74c890a in ?? ()
from /usr/lib/libpython3.6m.so.1.0
#13 0x00007ffff7404ad0 in _PyCFunction_FastCallDict ()
from /usr/lib/libpython3.6m.so.1.0
#14 0x00007ffff740e10f in _PyObject_FastCallDict ()
from /usr/lib/libpython3.6m.so.1.0
#15 0x00007ffff7492c5b in PyErr_PrintEx ()
3.6m.so.1.0
#16 0x00007ffff74939d1 in PyRun_SimpleFileExFlags ()
from /usr/lib/libpython3.6m.so.1.0
#17 0x00007ffff748970b in Py_Main ()
from /usr/lib/libpython3.6m.so.1.0
#18 0x0000555555554c39 in main ()
观察对PyErr_Display
和PyTraceBack_Print
的调用。看起来Python试图显示错误,但在此过程中崩溃了。确实,这不会崩溃:
try:
intbitset([x for x in arr])
except Exception as ex:
print(repr(ex))
相反,它输出以下内容:
ValueError('retrieving integers from rhs is impossible: invalid index to scalar variable.')
here in intbitset.__cinit__
提出了这个例外。请注意,__cinit__
是一个特殊的Cython函数。
它是针对另一个例外而提出的,来自gen_arrtype_subscript
的numpy C代码。它可以通过索引这样的标量来触发:
>>> import numpy as np
>>> arr = np.array([1,2,3,4,5])
>>> arr[0][0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: invalid index to scalar variable.
intbitset
触发此异常的原因是this line:
tuple_of_tuples = rhs and hasattr(rhs, '__getitem__') and hasattr(rhs[0], '__getitem__')
实际上,numpy标量(在这种情况下是numpy.int64
)确实有__getitem__
,如果你称之为它们就不喜欢它。这导致intbitset
错误地认为它正在接收sequence made of tuples,这会触发对异常提升__getitem__
的调用。
这解释了为什么如果你传递一个生成器intbitset(x for x in arr)
它没有崩溃:它没有__getitem__
,所以intbitset进入一个不同的代码路径。如果你直接传递intbitset(arr)
,tuple_of_tuples
线会在尝试将arr
转换为bool
时触发另一个异常:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
这个例外是numpy的非Pythonic(与列表转换为bool
的方式不一致),但这就是它的工作方式。
那么为什么invalid index to scalar
异常导致段错误,即使truth value of an array
没有?事实上,如果我把raise ValueError()
放在它之后,两者都会崩溃,所以很明显,两种情况下都会出现未定义的行为,而且运气有时也不会崩溃。
我的猜测是,intbitset
通过从__cinit__
提出异常来做出意想不到的事情。在Cython文档中没有明确禁止,所以我不确定如何或什么。