使用动态版本的Python执行嵌入式python代码时出现致命的Python错误

Cad*_*hon 14 c++ python python-embedding boost-python

SPOILER:部分解决(见最后).

以下是使用Python嵌入的代码示例:

#include <Python.h>
int main(int argc, char** argv)
{
    Py_SetPythonHome(argv[1]);
    Py_Initialize();
    PyRun_SimpleString("print \"Hello !\"");
    Py_Finalize();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我使用gcc 4.8.5在Linux openSUSE 42.2下工作(但我在使用gcc 4.8.3的openSUSE 13.2或使用gcc 4.4.7的RedHat 6.4时也遇到了同样的问题).

我编译了Python 2.7.9的静态和动态版本(但我在Python 2.7.13中也遇到了同样的问题).

我使用以下命令编译链接到Python静态版本的示例:

g++ hello.cpp -o hello \
-I /home/caduchon/softs/python/2.7.9/64/gcc/4.8.5/static/include/python2.7 \
-L /home/caduchon/softs/python/2.7.9/64/gcc/4.8.5/static/lib \
-l python2.7 -l pthread -l util -l dl
Run Code Online (Sandbox Code Playgroud)

如果我在参数中使用静态版本的Python执行我的示例,它就可以工作.

如果我在参数的动态版本的Python上执行它,我会收到以下错误(它发生在Py_Initialize()):

> ./hello /home/caduchon/softs/python/2.7.9/64/gcc/4.8.5/dynamic
Fatal Python error: PyThreadState_Get: no current thread
Aborted (core dumped)
Run Code Online (Sandbox Code Playgroud)

我不知道为什么它适用于静态版本,它不适用于动态版本.我该如何解决这类问题?

编辑:我安装Python的脚本如下:

#!/bin/bash

WORKDIR=/home/caduchon/tmp/install_python_2_7_13
ARCHIVEDIR=/home/caduchon/downloads/python
PYTHON_VERSION='2.7.13'
EZ_SETUP_VERSION='0.9'
SETUPTOOLS_VERSION='34.1.0'
CYTHON_VERSION='0.25.2'
NUMPY_VERSION='1.12.0'
SCIPY_VERSION='0.18.1'
MATPLOTLIB_VERSION='2.0.0'
INSTALLDIR=/home/caduchon/softs/python/$PYTHON_VERSION/64/gcc/4.8.5
LAPACKDIR=/home/caduchon/softs/lapack/3.6.1/64/gcc/4.8.5

### Tkinter ###
echo "Install Tkinter"
sudo apt-get install tk-dev

### Workdir ###
echo "Create workdir"
mkdir -p $WORKDIR/static
mkdir -p $WORKDIR/dynamic

### Python
for x in static dynamic
do
    echo "Install Python ($x)"
    cd $WORKDIR/$x
    echo "  extract archive"
    cp $ARCHIVEDIR/Python-$PYTHON_VERSION.tgz .
    tar -xzf ./Python-$PYTHON_VERSION.tgz &> archive.log
    cd ./Python-$PYTHON_VERSION
    echo "  configure"
    if [ "$x" = "static" ]
    then
        ./configure --prefix=$INSTALLDIR/$x --libdir=$INSTALLDIR/$x/lib &> configure.log
    else
        export LD_RUN_PATH=$INSTALLDIR/$x/lib
        ./configure --enable-shared --prefix=$INSTALLDIR/$x --exec-prefix=$INSTALLDIR/$x --libdir=$INSTALLDIR/$x/lib &> configure.log
    fi
    echo "  build"
    make &> make.log
    echo "  install"
    make install &> make_install.log
    echo "  done"
done

### setuptools
for x in static dynamic
do
    echo "Install setuptools ($x)"
    cd $WORKDIR/$x
    echo "  extract archives"
    cp $ARCHIVEDIR/ez_setup-$EZ_SETUP_VERSION.tar.gz .
    tar -xzf ./ez_setup-$EZ_SETUP_VERSION.tar.gz &> archive.log
    cp $ARCHIVEDIR/setuptools-$SETUPTOOLS_VERSION.zip .
    unzip ./setuptools-$SETUPTOOLS_VERSION.zip &> archive.log
    cp ./ez_setup-$EZ_SETUP_VERSION/ez_setup.py ./setuptools-$SETUPTOOLS_VERSION/.
    cd ./setuptools-$SETUPTOOLS_VERSION
    echo "  install"
    $INSTALLDIR/$x/bin/python ./ez_setup.py &> setup.log
    echo "  done"
done

### Cython
for x in static dynamic
do
    echo "Install Cython ($x)"
    cd $WORKDIR/$x
    echo "  extract archive"
    cp $ARCHIVEDIR/Cython-$CYTHON_VERSION.tar.gz .
    tar -xzf ./Cython-$CYTHON_VERSION.tar.gz &> archive.log
    cd ./Cython-$CYTHON_VERSION
    echo "  install"
    $INSTALLDIR/$x/bin/python ./setup.py install &> install.log
    echo "  done"
done

### NumPy
for x in static dynamic
do
    echo "Install NumPy ($x)"
    cd $WORKDIR/$x
    echo "  extract archive"
    cp $ARCHIVEDIR/numpy-$NUMPY_VERSION.zip .
    unzip ./numpy-$NUMPY_VERSION.zip &> archive.log
    cd ./numpy-$NUMPY_VERSION
    echo "  build"
    $INSTALLDIR/$x/bin/python ./setup.py build --fcompiler=gfortran &> build.log
    echo "  install"
    $INSTALLDIR/$x/bin/python ./setup.py install &> install.log
    echo "  done"
done

### SciPy
for x in static dynamic
do
    echo "Install SciPy ($x)"
    cd $WORKDIR/$x
    echo "  extract archive"
    cp $ARCHIVEDIR/scipy-$SCIPY_VERSION.tar.gz .
    tar -xzf ./scipy-$SCIPY_VERSION.tar.gz &> archive.log
    cd ./scipy-$SCIPY_VERSION
    echo "  configure"
    echo "[DEFAULT]" > ./site.cfg
    echo "library_dirs = $LAPACKDIR/lib64" >> ./site.cfg
    echo "search_static_first = true" >> ./site.cfg
    echo "  build"
    $INSTALLDIR/$x/bin/python ./setup.py build --fcompiler=gfortran &> build.log
    echo "  install"
    $INSTALLDIR/$x/bin/python ./setup.py install &> install.log
    echo "  done"
done

### MatPlotLib
for x in static dynamic
do
    echo "Install MatPlotLib ($x)"
    cd $WORKDIR/$x
    echo "  extract archive"
    cp $ARCHIVEDIR/matplotlib-$MATPLOTLIB_VERSION.tar.gz .
    tar -xzf ./matplotlib-$MATPLOTLIB_VERSION.tar.gz &> archive.log
    cd ./matplotlib-$MATPLOTLIB_VERSION
    echo "  build"
    $INSTALLDIR/$x/bin/python ./setup.py build &> build.log
    echo "  install"
    $INSTALLDIR/$x/bin/python ./setup.py install &> install.log
    echo "  done"
done
Run Code Online (Sandbox Code Playgroud)

编辑:我发现了问题的可能原因.如果我删除export LD_RUN_PATH=$INSTALLDIR/$x/lib动态Python安装中的行,我的嵌入代码就可以了.我sys.path通过嵌入式代码打印,它指向正确的安装.但是......通过这种方式我无法直接使用安装:它加载了系统中找到的错误版本(当我打印时,sys.path我看到它指向/ usr/...).另外,我不想设置环境变量来启动Python,因为我在同一台机器上使用了几个版本的Python.

编辑:保留我的Python默认安装脚本,我通过在编译C++示例时在选项中添加-rdynamic来解决问题.但我不太清楚这个选项到底是什么,以及它可能造成哪种灾难......

Man*_*cob 3

如果我理解正确的话,您希望运行静态链接版本,同时将 Python 主目录设置为动态链接版本。这是行不通的。

发生的情况如下:当您运行Py_Initialize()静态链接库时,它会在某个时候尝试导入模块_locale。因为您将 Python 主目录设置为动态链接版本,所以它将加载$INSTALLDIR/dynamic/lib/python2.7/lib-dynload/_locale.so. 该库动态链接到$INSTALLDIR/dynamic/lib/libpython2.7.so.1.0. 现在你最终得到了两份解释器的副本。第一个副本是静态链接的副本,正在初始化。第二个副本未初始化。当动态模块导入机制尝试初始化_locale模块时,它会失败,因为_locale的 init 函数引用了第二个完全未初始化的解释器。

您尝试此操作的原因是什么?如果您告诉我们您首先想解决哪个问题,我们也许可以帮助您。

编辑:(我在第一次编辑后写了这个,到目前为止我还没有尝试过会发生什么-rdynamic):当你不设置 时LD_RUN_PATH$INSTALLDIR/dynamic/lib/python2.7/lib-dynload/_locale.so会动态链接到系统的libpython2.7.so. 您没有看到错误的原因是导入_locale模块失败并出现 ImportError (而不是段错误),但此 ImportError 在解释器初始化期间被捕获(而之前无法捕获段错误)。但是,如果您尝试在嵌入式解释器中导入_locale(或任何其他扩展模块,例如_struct),您会收到如下错误:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: $INSTALLDIR/dynamic/lib/python2.7/lib-dynload/_locale.so: undefined symbol: PyUnicodeUCS2_FromObject
Run Code Online (Sandbox Code Playgroud)

编辑:hello.cpp针对静态 Python 版本进行编译时,通常大多数符号_PyThreadState_Current不会出现在动态符号表中。这就是为什么您最终会得到如上所述的“解释器的两个副本”和段错误。但是,当传递 时-rdynamic,这些符号最终会出现在动态符号表中,因此现在“动态”构建的 _locale.so 中的模块 init 函数引用了_PyThreadState_Current“静态”构建。不过,我仍然不相信您尝试做的事情(使用与“动态”构建的 Python 主目录链接到“静态”构建的程序)是一个好主意。;)