V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
acone2003
V2EX  ›  Python

求助:为什么 C++调用 Python 用控制台程序可以,用图形界面就不行呢?

  •  
  •   acone2003 · 2019-08-18 20:25:59 +08:00 · 3705 次点击
    这是一个创建于 1960 天前的主题,其中的信息可能已经有所发展或是发生改变。
    在我的 C++程序中需要调用 Python 做机器学习方面的工作,我的配置是 Win7 x64, Anaconda3.5.1(Python 3.6.4),另外安装了 XGBoost、LightGBM、TensorFlow、Kearas 包, C++程序用

    VS2010。为什么当 C++是控制台程序的时候就可以正常调用 Python,而换成带界面的 MFC 程序的时候就不行了呢? C++在运行到 PyObject * pyModule = PyImport_ImportModule(

    szPythonFilename );这句的时候总是返回空指针。这个问题困扰了我好几个星期了,其中尝试过升级 Python、换电脑、Win7 换成 Win10 都不行。希望各位大佬给指点指点,问题出在了哪里?

    Python 程序 Predict.py 的代码如下:

    '''python
    import xgboost
    #import lightgbm
    import sklearn
    import multiprocessing
    import numpy
    import keras
    import os

    #程序用到的所有机器学习模型的名字
    def GetAllModels():
    Models = ["LinearRegression", "LogisticRegression", "XGBRegressor", "XGBClassifier",
    "RandomForestRegressor", "RandomForestClassifier", "LGBMRegressor", "LGBMClassifier",
    "NetworkRegressor", "NetworkClassifier"]
    return Models

    #特征选择
    def SelectFeatures( AllFeatures, SelectedFeatureTags ):
    SelectedFeatureIndice = []
    for i in range( 0, len( SelectedFeatureTags ) ):
    if SelectedFeatureTags[ i ] == 1: SelectedFeatureIndice.append( i )
    SelectedFeatures = AllFeatures[ :, SelectedFeatureIndice ]
    return SelectedFeatures

    #模型的一些属性,如是分类器还是回归、是否有宏参等
    def GetModelOptions( ModelName ):
    IsClassifier, ParellelNum, CanUsePP, HaveMacroParams, IsNetwork = False, 1, True, True, False
    CpuNum = multiprocessing.cpu_count()

    Name = ModelName.lower()
    if "classifier" in Name or Name == "logisticregression" or Name == "svc" or Name == "gaussiannb":
    IsClassifier = True
    if Name == "svc" or Name == "svr":
    ParellelNum = CpuNum
    if "network" in Name or "lgbm" in Name:
    CanUsePP = False
    if Name == "linearregression" or Name == "gaussiannb":
    HaveMacroParams = False
    if "network" in Name:
    IsNetwork = True
    return IsClassifier, ParellelNum, CanUsePP, HaveMacroParams, IsNetwork

    #读取、预测各个模型并进行模型融合
    def PredictOneLayer( Features, PathAndBasicName, LayerName, Models ):
    ImputerFile = PathAndBasicName + "." + LayerName + ".MeanImputer.pkl"
    if not os.path.exists( ImputerFile ): return None, "Error! %s not exists."%ImputerFile
    Imputer = sklearn.externals.joblib.load( ImputerFile )
    Features = Imputer.transform( Features )

    ScalerFile = PathAndBasicName + "." + LayerName + ".Scaler.pkl"
    if not os.path.exists( ScalerFile ): return None, "Error! %s not exists."%ScalerFile
    Scaler = sklearn.externals.joblib.load( ScalerFile )
    Features = Scaler.transform( Features )

    PcaFile = PathAndBasicName + "." + LayerName + ".PCA.pkl"
    if os.path.exists( PcaFile ):
    Pca = sklearn.externals.joblib.load( PcaFile )
    Features = Pca.transform( Features )

    LayerPredicts = []
    for ModelName in Models:
    IsClassifier, ParellelNum, CanUsePP, HaveMacroParams, IsNetwork = GetModelOptions( ModelName )
    FeatureTagFile = PathAndBasicName + "." + LayerName + "." + ModelName + ".FeatureTags.npy"
    if IsNetwork:
    FeatureTagFile = PathAndBasicName + "." + LayerName + ".LinearRegression.FeatureTags.npy"
    if not os.path.exists( FeatureTagFile ): return None, "Error! %s not exists."%FeatureTagFile
    FeatureTags = numpy.load( FeatureTagFile )
    if Features.shape[1] != len( FeatureTags ): return None, "FeatureTags not match Feature shape"
    SelectedFeatures = SelectFeatures( Features, FeatureTags )

    if IsNetwork:
    ModelFile = PathAndBasicName + "." + LayerName + "." + ModelName + ".h5"
    if not os.path.exists( ModelFile ): return None, "Error! %s not exists."%ModelFile
    Model = keras.models.load_model( ModelFile )
    RawPredicts = Model.predict( SelectedFeatures )
    ModelPredicts = RawPredicts.flatten()
    else:
    if "xgb" in ModelName.lower():
    ModelFile = PathAndBasicName + "." + LayerName + "." + ModelName + ".model"
    if not os.path.exists( ModelFile ): return None, "Error! %s not exists."%ModelFile
    Model = xgboost.Booster( model_file = ModelFile )
    DMatrix = xgboost.DMatrix( SelectedFeatures )
    ModelPredicts = Model.predict( DMatrix )
    else:
    ModelFile = PathAndBasicName + "." + LayerName + "." + ModelName + ".pkl"
    if not os.path.exists( ModelFile ): return None, "Error! %s not exists."%ModelFile
    Model = sklearn.externals.joblib.load( ModelFile )
    if IsClassifier: ModelPredicts = Model.predict_proba( SelectedFeatures )[ :, 1 ]
    else: ModelPredicts = Model.predict( SelectedFeatures )
    LayerPredicts.append( ModelPredicts )

    LayerPredicts = numpy.transpose( numpy.array( LayerPredicts ) )
    return LayerPredicts, "OK"

    #主函数,对样本进行最终预测
    def FinalPredict( Features, PathAndBasicName, FinalModel ):
    Models = GetAllModels()
    L1Predicts, Comment = PredictOneLayer( Features, PathAndBasicName, "L1", Models )
    if L1Predicts is None: return None, Comment

    L2Predicts, Comment = PredictOneLayer( L1Predicts, PathAndBasicName, "L2", Models )
    if L2Predicts is None: return None, Comment

    FinalModels = []
    FinalModels.append( FinalModel )
    FinalPredicts, Comment = PredictOneLayer( L2Predicts, PathAndBasicName, "L3", FinalModels )
    if FinalPredicts is None: return None, Comment
    FinalPredicts = numpy.transpose( numpy.array( FinalPredicts ) )
    Predicts = FinalPredicts[0]
    Predicts = Predicts.tolist()
    return Predicts, "OK"
    '''

    C++的主要代码如下:

    '''python
    //构造函数,调用 python 的初始化工作
    CPython::CPython()
    {
    srand( 0 );
    Py_Initialize();
    InitNumpy();

    //将 Predict.py 所在的目录设置为工作目录,确保能够调用它
    char szWorkPath[ MAX_PATH ] = "";
    GetCurrentDirectory( sizeof(szWorkPath), szWorkPath );
    GetModuleFileName( NULL, szWorkPath, sizeof(szWorkPath) );
    char * p = szWorkPath + strlen( szWorkPath );
    while( *p != '\\' && *p != ':' && p > szWorkPath ) p--;
    *p = 0;
    SetCurrentDirectory( szWorkPath );
    PyRun_SimpleString( "import os,sys" );
    PyRun_SimpleString( "sys.path.append( os.getcwd() )" );
    //PyRun_SimpleString( "print( sys.path )" );
    }

    //析构函数
    CPython::~CPython()
    {
    Py_Finalize();
    }

    //初始化 Numpy
    int CPython::InitNumpy()
    {
    import_array();
    return 0;
    }

    //C++调用 Python 的主函数,按照标准流程一步一步进行
    bool CPython::PredictByPython( double * pddFeatures, int nFeatureNum, int nSampleNum, char * szPythonFilename,
    char * szPredictFuncName, char * szModelPathAndBasicName, char * szFinalModelName,
    double * pdPredicts, char * szErrorInfo, int nErrorInfoSize )
    {
    sprintf_s( szErrorInfo, nErrorInfoSize, "OK!" );

    npy_intp npyDims[ 2 ] = { nSampleNum, nFeatureNum };
    PyObject * pyArray = PyArray_SimpleNewFromData( 2, npyDims, NPY_DOUBLE, pddFeatures );
    PyObject * pyArguments = PyTuple_New( 3 );
    PyTuple_SetItem( pyArguments, 0, pyArray );
    PyTuple_SetItem( pyArguments, 1, Py_BuildValue( "s", szModelPathAndBasicName ) );
    PyTuple_SetItem( pyArguments, 2, Py_BuildValue( "s", szFinalModelName ) );

    bool bIsCorrect = true;
    PyObject * pyModule = PyImport_ImportModule( szPythonFilename );//当 MFC 运行到此时总是返回 NULL
    PyObject * pyDict = NULL, * pyFunc = NULL, * pyReSult = NULL;
    if( pyModule )
    {
    pyDict = PyModule_GetDict( pyModule );
    if( pyDict )
    {
    pyFunc = PyDict_GetItemString( pyDict, szPredictFuncName );
    if( pyFunc )
    {
    pyReSult = PyObject_CallObject( pyFunc, pyArguments );
    if( pyReSult )
    {
    PyObject * pyErrorInfo = PyTuple_GetItem( pyReSult, 1 );
    char * pszReturnedString = NULL, szReturnInfo[ 256 ] = "";
    if( pyErrorInfo )
    {
    PyArg_Parse( pyErrorInfo, "s", & pszReturnedString );
    strcpy_s( szReturnInfo, pszReturnedString );
    Py_DECREF( pyErrorInfo );
    }
    else strcpy_s( szReturnInfo, "Can not get returned error info." );

    PyObject * pyPredictResult = PyTuple_GetItem( pyReSult, 0 );
    if( PyList_Check( pyPredictResult ) )
    {
    int nResultNum = PyList_Size( pyPredictResult );
    if( nResultNum == nSampleNum )
    {
    for( int i=0; i<nResultNum; i++ )
    {
    PyObject * pyItem = PyList_GetItem( pyPredictResult, i );
    double dTheItemResult = 0.0;
    PyArg_Parse( pyItem, "d", & dTheItemResult );
    pdPredicts[ i ] = dTheItemResult;
    Py_DECREF( pyItem );
    }
    }
    else
    {
    bIsCorrect = false;
    sprintf_s( szErrorInfo, nErrorInfoSize, "Error! Returned predict num does not fit.%s", szReturnInfo );
    }
    }
    else
    {
    bIsCorrect = false;
    sprintf_s( szErrorInfo, nErrorInfoSize, "Error! Type of return is not correct.%s", szReturnInfo );
    }
    if( pyPredictResult ) Py_DECREF( pyPredictResult );
    }
    else
    {
    bIsCorrect = false;
    sprintf_s( szErrorInfo, nErrorInfoSize, "Error! Can not get predict result." );
    }
    }
    else
    {
    bIsCorrect = false;
    sprintf_s( szErrorInfo, nErrorInfoSize, "Error! No such function name: %s.", szPredictFuncName );
    }
    }
    else
    {
    bIsCorrect = false;
    sprintf_s( szErrorInfo, nErrorInfoSize, "Error! No functions in module." );
    }
    }
    else
    {
    bIsCorrect = false;
    sprintf_s( szErrorInfo, nErrorInfoSize, "Error! No python file name: %s.", szPythonFilename );
    }

    if( pyReSult ) Py_DECREF( pyReSult );
    if( pyFunc ) Py_DECREF( pyFunc );
    if( pyDict ) Py_DECREF( pyDict );
    if( pyModule ) Py_DECREF( pyModule );
    Py_DECREF( pyArguments );

    return bIsCorrect;
    }
    '''

    用控制台程序的测试代码如下:

    '''python
    #include "..\CPython.h"

    int main()
    {
    //随机生成 100 个样本,974 是特征维度,不能变
    int nFeatureNum = 974, nSampleNum = 100;
    double * pddFeatures = new double[ nFeatureNum * nSampleNum ];
    CPython::GenarateRandomTestFeatures( pddFeatures, nFeatureNum, nSampleNum );
    double * pdPredicts = new double[ nSampleNum ];
    memset( pdPredicts, 0, sizeof(double) * nSampleNum );
    char szErrorInfo[ 256 ] = "";
    //调用 Python
    CPython cPython;
    cPython.PredictByPython( pddFeatures, nFeatureNum, nSampleNum, "Predict", "FinalPredict",
    "D:\\C++ Programs\\TryPython\\Models\\T1_GuDiFanZhuan", "LinearRegression",
    pdPredicts, szErrorInfo, sizeof(szErrorInfo) );
    //输出结果
    printf( "Return info: %s\n", szErrorInfo );
    for( int i=0; i<nSampleNum; i++ )
    {
    printf( "%d %.6f\n", i, pdPredicts[ i ] );
    }

    delete [] pddFeatures;
    delete [] pdPredicts;
    return 0;
    }
    '''

    使用带界面的 MFC 程序的测试代码如下:

    '''python
    void CTryPythonInMFCDlg::OnBnClickedGo()
    {
    // TODO: 在此添加控件通知处理程序代码
    GetDlgItem( ID_GO )->EnableWindow( FALSE );
    GetDlgItem( IDC_RUN_INFO )->SetWindowText( "Running..." );

    //随机生成 100 个样本,974 是特征维度,不能变
    int nFeatureNum = 974, nSampleNum = 100;
    double * pddFeatures = new double[ nFeatureNum * nSampleNum ];
    CPython::GenarateRandomTestFeatures( pddFeatures, nFeatureNum, nSampleNum );
    double * pdPredicts = new double[ nSampleNum ];
    memset( pdPredicts, 0, sizeof(double) * nSampleNum );
    char szErrorInfo[ 256 ] = "";
    //调用 Python
    CPython cPython;
    cPython.PredictByPython( pddFeatures, nFeatureNum, nSampleNum, "Predict", "FinalPredict",
    "D:\\C++ Programs\\TryPython\\Models\\T1_GuDiFanZhuan", "LinearRegression",
    pdPredicts, szErrorInfo, sizeof(szErrorInfo) );
    //显示运行结果的信息
    GetDlgItem( IDC_RUN_INFO )->SetWindowText( szErrorInfo );

    delete [] pddFeatures;
    delete [] pdPredicts;

    GetDlgItem( ID_GO )->EnableWindow( TRUE );
    }
    '''

    另外,如果把 Predict.py 换成如下的极简函数,则无论控制台还是界面程序都能正常运行:

    '''python
    def FinalPredict( Features, PathAndBasicName, FinalModel ):
    Predicts = []
    for item in Features: Predicts.append( 0.5 )
    return Predicts, "OK!"
    '''

    希望各位给指点指点吧!
    17 条回复    2019-08-19 21:48:40 +08:00
    lcdtyph
        1
    lcdtyph  
       2019-08-18 22:02:41 +08:00
    试试一行一行地把你的原始 predict.py 里的 import xxx 加到后面极简 predict.py 里面,看加到哪一个开始返回 NULL 的
    tottea
        2
    tottea  
       2019-08-18 23:00:04 +08:00
    公司项目我也要用 C++调 Python,我直接用 http 来调用,简单
    autogen
        3
    autogen  
       2019-08-18 23:58:42 +08:00
    以后还是不要贴代码比较好。。。

    提供个思路:
    1. 输入保存成文件 in.txt
    2. WinExec('python3 xxx.py')
    3. py 执行结果保存到文件 out.txt
    4. C++程序读 out.txt
    no1xsyzy
        4
    no1xsyzy  
       2019-08-19 00:48:04 +08:00   ❤️ 3
    1. 不要贴大段代码,用网络剪贴板,比如 gist 或者 pastebin,一来掉缩进 python 就根本不能看(有歧义),二来大部分人看到大段代码第一反应就是 ^W,比如我刚才直接 ^W 了……
    2. 你问题之前问了一遍,到现在你什么进度都没有,你自己都不上心那么别人为什么要帮你?
    May725
        5
    May725  
       2019-08-19 00:49:48 +08:00
    实在不理解。会有多少人把这段长代码读一遍
    ysc3839
        6
    ysc3839  
       2019-08-19 01:24:09 +08:00 via Android
    PyImport_ImportModule 失败之后调用一下 PyErr_Print 看看错误信息,不过得先把 sys.stderr 替换成 StringIO,调用后再取出数据。
    acone2003
        7
    acone2003  
    OP
       2019-08-19 05:17:20 +08:00
    真心感谢楼上各位的建议,待我都试一试。发现一个特点:大佬级的人物都是夜猫子啊。
    @tottea:怎么用 http 调用?愿闻其详。
    @autogen:目前我正是用的这个临时的替代办法。
    @no1xsyzy:怎么用网络剪贴板?能不能给一个说明的链接什么的?
    janxin
        8
    janxin  
       2019-08-19 07:26:37 +08:00 via iPad
    Cooky
        9
    Cooky  
       2019-08-19 07:38:38 +08:00 via Android
    别人贴一大堆你会看?
    tempdban
        10
    tempdban  
       2019-08-19 07:50:34 +08:00 via Android
    我猜是不是没有 stdin / out / err
    frostming
        11
    frostming  
       2019-08-19 07:51:03 +08:00
    markdown 代码格式是用反引号``` 不是'''啊
    secsilm
        12
    secsilm  
       2019-08-19 08:24:21 +08:00 via Android
    @acone2003 HTTP 调用应该指的是用 Python 启个服务,c++调这个服务就行,这样就语言无关了
    no1xsyzy
        13
    no1xsyzy  
       2019-08-19 08:53:54 +08:00
    @acone2003 pastebin.com 或者 gist.github.com ,本身功能简单界面直观所以没什么说明的必要
    Hconk
        14
    Hconk  
       2019-08-19 12:21:02 +08:00 via iPhone
    我最近也在做这方面,同样 C++调用 python,不过我是用的 QT,遇到过和你类似的问题。说一下我遇到的问题给你参考下。
    我的 python 环境用的是 anaconda 管理的,默认版本是 3.7,电脑没有 gpu 就只能用 conda 建了个 tensorflow 1.2.1 的环境,这样会有问题,系统环境变量里面的 python 没有包含我要用到的包,import 会失败,如果你也是类似的环境可以尝试下在系统环境变量的 python 安装好需要的包,测试下你的 python 程序。
    另外你可以测试下在你那个简单的 python 程序上加入 import 你项目 python 程序要用到的包测试下是否正常,检查是不是由于哪个包引起的。
    还可以通过修改你原来的程序,逐渐增加代码看是那个地方引起的导入失败。
    acone2003
        15
    acone2003  
    OP
       2019-08-19 18:41:31 +08:00
    @Hconk :谢谢你!我的环境和你的类似,也是没有 GPU,用 pip install 了一个 tensorFlow。待我按你的方法试一试。
    acone2003
        16
    acone2003  
    OP
       2019-08-19 20:41:08 +08:00
    @Hconk :果真是 TensorFlow 出了问题,我把 import keras 去掉之后就 OK 了,keras 的后端是 TensorFlow。你能不能简单回忆一下当初采取哪些步骤解决这个问题的?让我少采点坑,我对 Python 不太熟。

    在此一并感谢 lcdtyph 以及楼上各位大佬!!
    Hconk
        17
    Hconk  
       2019-08-19 21:48:40 +08:00 via iPhone
    @acone2003 我最开始装的是最新版本的 anaconda,自带的 python 版本是 3.7,我要装 tensorflow 1.2.1 支持 CPU,遇到这个问题后我卸载最新版装了自带 python 3.6.x 的 anaconda3-5.2.0。(我看你装的已经是这个版本了,应该不需要更换)
    重装后直接在默认环境下安装了 tensorflow,keras 等需要的包。直接打开 cmd 到你的 c++程序下面执行一下测试 python 程序能否正常运行,不要用 conda 选环境试下。正常运行应该环境没问题。

    另外一个我遇到的问题是模型路径的问题,如果模型 python 程序里面加载模型的路径不对找不到文件会也会导致 import 失败,可以通过先把模型都通过绝对路径加载的形式测试下是不是这个问题引起的。

    另外我的系统环境变量里面 python 相关的加了
    ```
    E:\Program Files\Anaconda;
    E:\Program Files\Anaconda\Library\mingw-w64\bin;E:\Program Files\Anaconda\Library\usr\bin;
    E:\Program Files\Anaconda\Library\bin;
    E:\Program Files\Anaconda\Scripts;
    ```
    这些,我不确定是否有关系你检查下不同的话可以尝试下。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2825 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 14:28 · PVG 22:28 · LAX 06:28 · JFK 09:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.