从 C++ 应用程序中的嵌入式 Python 调用时,多数组扩展库上的 Numpy 导入失败

Posted

技术标签:

【中文标题】从 C++ 应用程序中的嵌入式 Python 调用时,多数组扩展库上的 Numpy 导入失败【英文标题】:Numpy import fails on multiarray extension library when called from embedded Python within a C++ application 【发布时间】:2018-09-21 21:36:21 【问题描述】:

我正在运行一个 C++ 应用程序,它尝试使用 https://docs.python.org/3.5/extending/embedding.html 函数调用来运行 python。这是应用程序错误消息管道给我的错误。

类'ImportError': 导入多数组 numpy 扩展模块失败。最多 您可能正在尝试导入失败的 numpy. 如果您正在使用 numpy git repo,请尝试 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 并启用共享 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。 使用pip3.5 install numpy 命令使用pip 9.0.0 安装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 时无法导入多数组组件和 numpy;但是,要么它们都不符合我的具体情况,要么正如我所说的那样存在一个漏洞。以下是我可能会问的子问题列表,如果没有人在此设置中看到任何明显令人担忧的内容,我可能会问这些问题。当/如果我提出问题时(在我完善它们之后),我可能会用链接更新问题。

numpy multiarray.so 如何链接到 pythonX.X.so 进行符号解析? ldd 似乎并没有暗示它曾经这样做过。在link问了这个问题 在 2018 年 4 月 12 日提出的 question 中解决了与 CMake 问题无关的问题,并于 2018 年 4 月 16 日回答。 在 .bashrc 中设置 PYTHONPATH 似乎不会更新 Py_GetPath() 返回的内容,我必须通过不同的方法将导入的站点包添加到 sys.path。它可能只更新不影响 C++ 的 bash 脚本环境变量。

此时,我并不是要对上述问题列表寻求答案,而是要提供更多线索来说明我的知识差距可能在哪里。

感谢您抽出宝贵时间阅读此问题。任何帮助表示赞赏。

编辑:2018 年 4 月 17 日:

嗯,我找到了一个解决方法,我目前正在使用它。沙丘问题开始让我更仔细地思考未定义的符号以及它如何可能是链接器/编译器错误,或者 numpy 导入总是需要一个已经将这些符号加载到内存中的环境。这让我尝试安装不同版本的 numpy 以查看是否有任何旧版本有所作为。他们没有,但它确实使抛出的错误略有不同。当我用谷歌搜索时,这是question appeared。接受的答案通过将这两行添加到 pythonInterface.cpp 给了我一个解决方法:

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

这些命令添加要加载的共享库,并可用于 cpython.multiarray.so。

这不是一个理想的解决方案,因为它指向一个特定的 .so,它可能因机器而异。它现在解决了这个问题,但它也可能导致错误,如果与 pythonInterface.so 的链接库发生更改,则在 python 调用过程中可能会出现共享库不匹配的错误,并且此行没有得到更新。我相信如果这个sub-question 得到回答,可以得到更好的答案,所以我目前正在坚持提交或接受答案,直到那时。谢谢!

【问题讨论】:

你是如何编译你的代码的?这听起来像链接器不知道它需要链接给定的符号。在命令行上,应始终在使用它们的对象之后指定库。见***.com/questions/11643666/… @Dunes 感谢您的意见!对于您的问题,我不确定如何编译 numpy/根据 numpy multiarray 子问题链接的符号。我目前的理论是 cpython multiarray .so 期望 python3.5m.so 以某种方式加载到内存中以进行 numpy 导入;但是,由于某种原因,当使用 Py_Initialize() 进行 python 嵌入时,它没有加载到内存中,而是用于我的终端 pythonX.exe 调用。我已经找到了解决此问题的方法,如上面帖子中的编辑所示。我对此不满意,因为它是一个混乱的解决方案(需要多台机器的逻辑) 你能提供一个最小的例子来说明你的问题吗?我试图在我的机器上重现你的问题,一切似乎都很好。我使用PyRun_SimpleString 导入numpy,创建一个数组,用它做算术并打印出来。我也试过直接导入numpy.core.multiarray 你查到这件事的底部了吗?我有同样的问题 【参考方案1】:

根本原因

出现此错误是因为 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 加载 multiarray.cpython-35m-x86_64-linux-gnu.so 时使用 dlopenlibdl.so 将尝试通过检查 python 主程序的依赖共享库来解析未定义的符号。它会在libpython.x.x.so 中找到它。

解决方案

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

    使用dlopen("libpythonx.x.so", RTLD_GLOBAL)。打开此so 使用RTLD_GLOBAL 标志后,它使libpythonx.x.so 中的符号可用于随后加载的共享对象的符号解析。 在嵌入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 符号。

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

【讨论】:

以上是关于从 C++ 应用程序中的嵌入式 Python 调用时,多数组扩展库上的 Numpy 导入失败的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ 调用 Python

从 C++ 嵌入式解释器捕获 python 窗口输出

从 C++ 停止嵌入式 Python 提示符

如何使用 QT/python 从 Javascript 调用 C++ 函数?

在 C++ 中嵌入 Python:解释器在执行过程中的持久性

嵌入Python | 调用Python模块中有参数的函数