从C++应用程序中调用的嵌入式Python中,当导入multiarray扩展库时,Numpy导入失败

17
我正在运行一个C++应用程序,该应用程序尝试使用https://docs.python.org/3.5/extending/embedding.html函数调用来运行Python。这是应用程序错误消息管道给我的错误信息。 : 导入multiarray numpy扩展模块失败。很可能您正在尝试导入numpy的失败构建版本。如果您正在使用numpy git存储库,请尝试git clean -xdf(删除所有未受版本控制的文件)。否则重新安装numpy。 原始错误是:/usr/local/lib/python3.5/site-packages/numpy/core/multiarray.cpython-35m-x86_64-linux-gnu.so:未定义的符号:PyExc_UserWarning
我感到困惑,因为只有在将Python嵌入C++中时才会出现此问题,而当我通过解释器使用它时,导入工作正常。我更感兴趣的是能够增加我的理解而不是快速解决此问题。我列出了一些系统/问题信息以及我正在考虑发布有关同一主题的其他问题。感谢任何指导!
  • Ubuntu 16.04, 64位
  • 编译Python 3.5.5并启用shared
  • numpy在解释器(python3.exe和python3.5.exe)中可导入
  • 我确保PySys_SetPath()设置的sys.path与解释器输出的相同:import syssys.path
  • 我可以导入其他模块,如PIL和datetimeutil;但是,无法导入numpy和pandas(pandas使用numpy或似乎是这样)
  • 嵌入式Python使用以下命令:Py_Import_Import()Py_Initialize()(我确定只调用了一次)。等等,但它没有在解释器上获取全局锁。
  • 该应用程序使用CMake构建系统构建,该系统编译为我的系统的MakeFiles。
  • 使用pip 9.0.0使用pip3.5 install numpy命令安装了numpy-1.14.2
  • 引起此错误的Python脚本只有一行:import numpy...
  • 我没有从中导入文件的.zip文件。
  • 嵌入在C++中的Python使用的.exe位于/usr/local/bin/python3(使用Py_GetProgramName()确定此)。这个.exe链接到libpython3.5m.so.1.0,缺少的符号位于libpython3.5m.so.1.0中(运行nm)
  • multiarray.cpython-35m-x86_64-linux-gnu.so的ldd显示:

    ldd multiarray.cpython-35m-x86_64-linux-gnu.so

    linux-vdso.so.1 => (0x00007ffd9e36b000)

    libopenblasp-r0-39a31c03.2.18.so => /usr/local/lib/python3.5/site-packages/numpy/core/./../.libs/libopenblasp-r0-39a31c03.2.18.so (0x00007fdbe149b000)

    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdbe1192000)

    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fdbe0f75000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdbe0bab000) /lib64/ld-linux-x86-64.so.2 (0x00007fdbe3ed5000)

    libgfortran-ed201abd.so.3.0.0 => /usr/local/lib/python3.5/site-packages/numpy/core/./../.libs/libgfortran-ed201abd.so.3.0.0 (0x00007fdbe08b1000)

我可以尝试通过不同的方式重新安装numpy,但是我很难追踪为什么这样做可能有效。此时,我认为我的知识存在一些漏洞。我查看了许多类似的帖子,关于在C++中嵌入Python时无法导入multiarray组件和numpy,但是,要么它们都不符合我的特定情况,要么就像我所说的存在问题。以下是一些子问题列表,如果没有人发现此设置中明显有问题,我可能会问这些问题。我可能会在以后更新问题并附上链接(在我整理之后)。
  • numpy multiarray.so如何链接到pythonX.X.so以进行符号解析?ldd似乎并没有表明它曾经这样做过。在link上提出了这个问题
  • CMake问题非相关问题已在question上解决,该问题于4/12/18提出,4/16/18回答。
  • 在.bashrc中设置PYTHONPATH似乎不能更新Py_GetPath()返回的内容,我必须通过其他方法添加site-packages以使其能够通过sys.path进行导入。它可能只更新bash脚本环境变量,而不影响C++。
我目前不要求回答上述问题列表,而是提供更多线索,以说明我的知识盲区可能在哪里。感谢您抽出时间阅读这个问题。任何帮助都将不胜感激。

编辑:2018年4月17日:

好的,我找到了一种解决方法,并正在使用它。Dunes的问题让我更加密切地思考未定义符号以及它可能是链接器/编译器错误,或者numpy导入始终期望已将这些符号加载到内存中的环境。这使我尝试安装不同版本的numpy,看看是否有任何旧版本有所不同。它们没有,但确实使错误抛出略有不同。当我谷歌搜索时,出现了这个问题。接受的答案通过将以下两行添加到pythonInterface.cpp中为我提供了解决方法:

  • #include <dlfcn.h>
  • dlopen("libpython3.5m.so.1.0", RTLD_LAZY | RTLD_GLOBAL)

这些命令将共享库添加到加载并可用于cpython.multiarray.so中。

这并不是一个理想的解决方案,因为指向特定的.so文件可能会因机器而异。虽然它暂时解决了问题,但在Python调用过程中,如果链接到pythonInterface.so的共享库发生更改,而此行未更新,则可能导致共享库不匹配的错误。我认为,如果回答了sub-question,就可以得到更好的答案,因此我目前还没有提交或接受答案。谢谢!

1
你是如何编译你的代码的?这听起来像链接器不知道它需要链接给定的符号。在命令行上,库应该始终在使用它们的对象之后指定。请参见 https://dev59.com/Mmgu5IYBdhLWcg3wBym1 - Dunes
@Dunes 感谢您的建议!针对您的问题,我不确定numpy是如何编译/链接符号的,特别是numpy multiarray子问题。我的当前理论是,cpython multiarray .so期望以某种方式将python3.5m.so加载到内存中以进行numpy导入;然而,使用Py_Initialize()进行Python嵌入时,由于某种原因它没有被加载到内存中,但是在我的终端pythonX.exe调用中却被加载了。我已经找到了解决这个问题的方法,如上面帖子中的编辑所示。虽然这是一个混乱的解决方案(需要适用于多台机器的逻辑),但我并不满意。 - skincell
你能提供一个最小的示例来展示你的问题吗?我正在尝试在我的机器上重现你的问题,但似乎一切都正常。我使用PyRun_SimpleString来导入numpy,创建一个数组,进行算术运算并打印输出。我还尝试直接导入numpy.core.multiarray - Dunes
你解决了这个问题吗?我也遇到了同样的问题。 - Dominic Bou-Samra
这是我唯一有效的解决方案。尤其奇怪的是,如果我使用Py_Initialize,那么与libpythonX.X.so的链接已经完成了。为什么我们还需要dlopen它呢? - undefined
2个回答

10

根本原因

这个错误是由于numpy中的multiarray.cpython-35m-x86_64-linux-gnu.so模块依赖于libpythonx.x.so,但没有明确地链接libpythonx.x.so导致的。所以,如果您使用ldd -d multiarray.cpython-35m-x86_64-linux-gnu.so命令,您将看不到Python在列表中。

Python没有问题,因为Python二进制文件依赖于libpython.x.x.so,因此当numpy使用dlopen加载multiarray.cpython-35m-x86_64-linux-gnu.so时,libdl.so会尝试通过检查主程序的依赖共享库来解决未定义的符号,也就是Python。它会在libpython.x.x.so中找到它。

解决方案

知道了根本原因,解决方案就很容易了,只需要帮助libdl.so能够找到libpython.x.x.so。有至少两种方法可以实现:

  1. 使用dlopen("libpythonx.x.so", RTLD_GLOBAL)函数。打开此so后使用RTLD_GLOBAL标志,它使得libpythonx.x.so中的符号可以供随后加载的共享对象的符号解析使用。
  2. 在嵌入Python的主程序中,将libpythonx.x.so添加到其依赖库中。

2

我曾经遇到一个类似的错误,那是在将应用程序与 libpython3.5m.a(归档文件,不是动态文件)进行链接时。一旦它加载了像 multiarray.cpython-35m-x86_64-linux-gnu.so 这样的文件,它就会期望存在像 PyFloat_Type 这样的符号。

在诊断为什么 Python 可以直接调用而我的应用程序却无法工作时,我注意到 readelf -s myapplication.symtab 表中有 PyFloat_Type 符号,但在 .dynsym 表中没有。

然而,readelf -s /asb/path/to/python3 在两个表中都有 PyFloat_Type 符号。

在 CMake 中添加: target_link_options(myapplication PUBLIC "LINKER:-export-dynamic") 可以确保所需的符号也在 .dynsym 表中可用。之后,应用程序就可以正常工作了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接