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

14

剧透警告:已部分解决(请查看结尾)。

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

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

我在Linux openSUSE 42.2下使用gcc 4.8.5工作(但是我在使用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

如果我使用静态版本的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)

我不知道为什么静态版本可以工作而动态版本不能。我该如何解决这种问题?

编辑:我安装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

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

编辑:保留我的默认Python安装脚本,通过在编译C++示例时添加选项-rdynamic来解决问题。但我不太明白这个选项是什么,以及会引起哪些问题...


尝试添加以下参数:-lboost_python -lpython2.7 - Hugo Corrá
@HugoCorrá:然后我遇到了一个需要动态库的错误。 - Caduchon
@HugoCorrá 我需要一个静态链接。 - Caduchon
我记得在某个时候(也许是在调查[SO]: What files are required for Py_Initialize to run时?)我遇到了确切的问题(好吧,没有涉及_Boost_),但我记不起解决方法了。 PythonBoost 文件夹是通过_make_或_make install_生成的结果吗?如果_Boost_库已经链接到(静态)_Python_库,那么后者在链接时是否需要由可执行文件使用? - CristiFati
我想我的第一个问题的答案是make install。此外,你能分享一下两个编译版本的_configure_命令(如果设置了任何特定的环境变量:例如_CFLAGS_?或任何其他自定义步骤)吗?通过运行PYTHONHOME=/home/caduchon/softs/python/2.7.9/64/gcc/4.8.5/dynamic /home/caduchon/softs/python/2.7.9/64/gcc/4.8.5/static/bin/python -c "print \"abcd\""可以复现该问题吗? - CristiFati
显示剩余8条评论
1个回答

3
如果我理解正确,您想在设置Python home为动态链接版本的同时运行静态链接版本。这是不可行的。
具体过程如下:当您运行静态链接库的Py_Initialize()时,它会尝试导入_locale模块。因为您将Python home设置为动态链接版本,它会加载$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
编辑:在编译 hello.cpp 与静态Python版本时,像 _PyThreadState_Current 这样的大多数符号通常不会出现在动态符号表中。这就是为什么你会得到上面描述的“两个解释器副本”和段错误。然而,当传递 -rdynamic 时,这些符号将出现在动态符号表中,因此现在来自“动态”构建的_ locale.so的模块初始化函数引用“静态”构建的 _PyThreadState_Current 。我仍然不确定你想做的事情(使用针对“动态”构建的Python主目录链接的程序)是否是一个好主意。 ;)

我的客户使用Python和PySide(Qt)(仅使用动态Python)。但我们不想在我们的可执行文件中提供动态库,因此我们需要进行静态链接。 实际上,这是可能的,以前也可以。但我不知道如何做。我无法复制以前的测试。我可能在我的配置中有问题,但我找不到它。 - Caduchon
PySide “只能与动态 Python 一起使用”这句话的意思是什么? - Manuel Jacob
无法在静态 Python 版本上安装 PySide,需要使用动态版本。 - Caduchon
请看我问题中的最后一个编辑。 - Caduchon
@Caduchon 我刚试着用“静态”Python构建编译PySide,但它确实失败了,抱怨一些代码没有使用-fPIC编译。我会尝试找出原因的。为什么你不能使用带有--enable-shared的Python构建呢?另外,我稍微扩展了我的答案,希望这样能让你更容易理解正在发生的事情。 - Manuel Jacob

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