【翻译】将Python嵌入到另一个应用程序中--from官网

本文介绍如何在C++项目中调用PyTorch训练好的模型,涉及Python嵌入C++程序的方法,包括初始化Python解释器、执行Python脚本、调用Python函数及数据类型转换等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近因为项目上需要用C++调用pytorch训练好的模型,不得已开始查这方面的资料,正好看到官方文档的第五章将Python嵌入到另一个应用程序中有讲到这方面的知识,正好翻译一下,也贡献给有需要的小伙伴。(注意:本章中主要讲到的是将python嵌入C程序中)
ps: 文中部分翻译可能不太恰当,欢迎小伙伴们进行批评指正~

5.在另一款应用程序中植入python

在之前的章节中已经讨论了如何扩展python,即,如何给python附加一个C函数库来扩展其功能。实际上,反过来做也是可行的:通过植入python来丰富你的C/C++程序。通过植入python你可以在你的程序中实现一些你用python编写的功能,而不是C/C++。这可以用来满足多种需求,其中一个例子是允许用户根据自己的需要写一些python脚本来定制应用程序。当然,如果一些功能使用python编写起来要更容易,你也可以这么做。

嵌入python类似于扩展它,但并不完全如此。区别在于,当你扩展python时,应用程序中的主程序依然是依赖于python解释器;而当你需要嵌入python时,你的主程序实际上已经和python没什么关系了都——相反,应用程序中的某些部分只会偶尔需要调用python解释器来运行其中的python代码。

因此,如果你正要嵌入python,你得提供你自己的主程序。你的主程序必须要做的事情之一是你必须去初始化python解释器。起码你得调用你的Py_Initialize()函数。还有一些可选的调用将命令行参数传给python。之后,你就可以在你应用程序的任何部位去调用这个解释器了。

调用这个解释器的途径有很多:你可以传给PyRun_SimpleString()一个包含python语句的字符串,或者你也可以传给PyRun_SimpleFile()一个stdio文件指针和一个文件名(这仅用于识别错误消息)。此外,你还可以调用前几章中描述得较低级别的操作来构造和使用python对象。

你可以在发行版的源代码下的 ***Demo/embed/***目录中找到一个嵌入python的简单示例。

另请参阅:

Python/C API 参考手册

本手册提供了Python C接口的详细信息。在这里可以找到大量必要的信息。

5.1 the very high level 接口

嵌入python的最简单形式是使用***the very high level *** 接口,这个接口不需要与应用程序直接交互,就可以被用来执行python脚本。例如,这可以用于对文件执行某些操作。

#include <Python.h>

int main(int argc, char *argv[])
{
  Py_SetProgramName(argv[0]);  /* optional but recommended */
  Py_Initialize();
  PyRun_SimpleString("from time import time,ctime\n"
                     "print 'Today is',ctime(time())\n");
  Py_Finalize();
  return 0;
}

Py_SetProgramName()函数应该在Py_Initialize()函数之前被调用,目的是告知解释器python运行时库的路径。接下来,python解释器会通过Py_Initialize()函数进行初始化,然后执行硬编码的Python脚本,打印日期和时间。随后,调用Py_Finalize()函数关闭解释器,然后程序结束。在实际的程序中,你可能希望从另一个源(可能是文本编辑器例程、文件或数据库)获得python脚本。但从文件中获取python代码的更好方式是使用PyRun_SimpleFile()函数,这样就省去了分配内存空间和加载文件内容的麻烦。

5.2 超越非常高水平的嵌入:概述

高级接口的确可以使你从应用程序中执行任意的python代码片段,但不得不说,交换数据值相当麻烦。如果你想要这样做,你应该使用更低级别的调用。当然,如果你不在乎编写更多更长的C代码,那你几乎可以实现任何目标。

值得注意的是,扩展python和嵌入python是几乎完全相同的任务,尽管他们的出发点是不同的。前几章讨论过的大多数主题依然有效。为了说明这一点,我们不妨以从python到C的扩展代码为例,看它究竟实际做了什么:
1.将数据值从python转换为C
2.使用转换后的值对C例程执行函数调用
3.将调用中的数据值从C转换成Python

当嵌入python时,这个接口代码做的工作如下:
1.将数据值从C转换为python
2.使用转换后的值对python接口例程执行函数调用
3.将调用中的数据值从python转换为C

如你所见,数据转换步骤简单进行了交换以适应跨语言传输时的不同转换方向。唯一的区别在于两种数据转换间调用的例程。当你需要扩展python时,调用C例程;当你需要嵌入python时,调用python例程。

本章我们不讨论如何将数据从python转换为C,反之亦然。此外,正确地使用引用和处理错误是可以理解的。这些方面与扩展解释器并没什么太大区别,你可以参考前面的章节以获得所需的信息。

5.3 纯粹的嵌入

下面的第一个程序目的是执行python脚本中的一个功能。正如我们在5.1节中提到的,python解释器不会直接与应用程序交互(但这点在下一节中将有所不同)

运行python脚本中定义的函数代码如下:

#include <Python.h>

int main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) {
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    }

    Py_Initialize();
    pName = PyString_FromString(argv[1]);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);
            for (i = 0; i < argc - 3; ++i) {
                pValue = PyInt_FromLong(atoi(argv[i + 3]));
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return 1;
                }
                /* pValue reference stolen here: */
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Result of call: %ld\n", PyInt_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    }
    Py_Finalize();
    return 0;
}

这个代码块使用命令行参数 ***argv[1]***来载入python脚本,并且调用参数***argv[2]***中命名的函数。它的整数参数是argv数组的其他值。如果你编译并链接这个程序(我们不妨将完成的可执行程序称之为 call ),并且使用它来执行python脚本,例如:

def multiply(a,b):
    print "Will compute", a, "times", b
    c = 0
    for i in range(0, a):
        c = c + b
    return c

随后将得到:

$ call multiply multiply 3 2
Will compute 3 times 2
Result of call: 6

尽管该程序的功能块看起来非常庞大,但其实大部分都是用于python和C之间的数据转换,以及错误报告。关于嵌入python,下面开始才是真正有趣的部分:

Py_Initialize();
pName = PyString_FromString(argv[1]);
/* Error checking of pName left out */
pModule = PyImport_Import(pName);

在对解释器进行初始化之后,使用PyImport_Import()加载脚本。这个例程需要一个python字符串作为它的参数,该参数是通过使用 PyString_FromString()来构造的。

pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc is a new reference */

if (pFunc && PyCallable_Check(pFunc)) {
    ...
}
Py_XDECREF(pFunc);

一旦脚本被载入,我们使用 PyObject_GetAttrString()检索我们需要查找的名称。如果名称存在,并且其返回对象是可调用的,我们就大可以假设它是一个函数。然后,程序将按照常规构造一个元组参数,并使用以下命令调用python函数:

pValue = PyObject_CallObject(pFunc, pArgs);

函数返回时,pValue 要么为空,要么包含对函数返回值得应用。在检查完值之后,一定要记得释放引用。

5.4 嵌入式python扩展

到目前为止,嵌入式python解释器无法访问应用程序本身的功能,而python API通过扩展嵌入式解释器实现了这一点。也就是说,使用应用程序提供的例程扩展嵌入式解释器。虽然这听起来很复杂,但其实也没那么糟。先暂时忘记应用程序启动python解释器。相反,我们将应用程序视为一组子例程,并编写一些使python能够访问这些例程的粘合代码,就像编写一般的python扩展一样。例如:

static int numargs=0;

/* Return the number of arguments of the application command line */
static PyObject* emb_numargs(PyObject *self, PyObject *args)
{
    if(!PyArg_ParseTuple(args, ":numargs"))
        return NULL;
    return Py_BuildValue("i", numargs);
}

static PyMethodDef EmbMethods[] = {
    {"numargs", emb_numargs, METH_VARARGS,
     "Return the number of arguments received by the process."},
    {NULL, NULL, 0, NULL}
};

在main()函数之上插入上述代码。另外,在Py_Initialize() 之后直接插入以下两条语句:

numargs = argc;
Py_InitModule("emb", EmbMethods);

这两行代码初始化了***numargs***变量,并使嵌入式python解释器能够访问***emb.numargs()***函数。有了这些扩展,python脚本可以做以下事情:

import emb
print "Number of arguments", emb.numargs()

在实际应用程序中,这些方法将向python公开应用程序的API。

5.5 在C++中嵌入python

在C++程序中嵌入python也是可能地;具体如何实现取决于所使用的C++系统的细节;通常,我们需要使用C++编写主程序,并使用C++编译器来编译和链接我们的程序,而不需要使用C++来重新编译python本身。

5.6 在类Unix系统下的编译和链接

为了将python解释器嵌入到应用程序中,找到传递给编译器(和链接器)的正确标志并不是件小事,这是因为python需要加载作为C动态扩展来实现(.so文件)的库模块,并将其链接到应用程序中。

为了找到所需的编译器和链接器标志,可以执行在安装过程中生成的***pythonX.Y-config*** 脚本(也可以使用***python-config***脚本)。这个脚本有好几个选项,下面的选项可能对你有用:
当你编译时***pythonX.Y-config --cflags***将给你推荐的标志:

$ /opt/bin/python2.7-config --cflags
-I/opt/include/python2.7 -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes

当你链接时,***pythonX.Y-config --ldflags***将给你推荐的标志:

$ /opt/bin/python2.7-config --ldflags
-L/opt/lib/python2.7/config -lpthread -ldl -lutil -lm -lpython2.7 -Xlinker -export-dynamic

注意:为了避免在几个python安装之间(特别是在系统python和你自己编译的python)产生混淆,建议使用***pythonX.Y-config*** 的绝对路径,如上例所示。

如果此步骤对你来说并不适用(它并不能保证适用所有的类unix平台;但我们同样欢迎bug报告),你必须阅读系统关于动态链接和/或检查python的Makefiles(使用sysconfig.get_makefile_filename()查找其位置)和编译选项。在这种情况下,***sysconfig***模块是一个有用的工具,可以通过编程方式提取你想要组合在一起的配置值。例如:

>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl  -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'

--------------------------------------------------------------------------------------------------------这是一条严肃的分割线

翻译完也没能搞明白C++是怎么调用Python的,确实是条废柴没错了,哭!

以下是使用Flask创建Web应用程序的基本步骤: 1. 安装Flask:在终端中使用pip命令安装Flask。命令如下: ``` pip install flask ``` 2. 创建Flask应用程序:在Python文件中导入Flask库,并创建一个Flask应用程序。代码如下: ``` from flask import Flask app = Flask(__name__) ``` 3. 定义路由:使用@app.route()装饰器定义一个路由,该路由指定了应用程序的URL地址和处理该地址的Python函数。代码如下: ``` @app.route('/') def hello_world(): return 'Hello, World!' ``` 4. 运行应用程序:在Python文件的末尾添加以下代码,以便在本地主机上运行应用程序。 ``` if __name__ == '__main__': app.run() ``` 5. 部署应用程序:将Python文件上传到Web服务器上,并使用Flask提供的Web服务器来运行应用程序。Flask提供了两个Web服务器:Werkzeug和Gunicorn。以下是使用Werkzeug Web服务器的示例代码: ``` from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ``` 在Web服务器上运行应用程序时,需要使用以下命令: ``` FLASK_APP=app.py flask run ``` 注意,将上述示例中的app.py替换为你的Python文件名。外,你还需要在Web服务器上安装Flask库和Werkzeug库,以便正确运行应用程序。 以上是使用Flask创建Web应用程序的基本步骤。你可以根据自己的需求和应用场景,进一步完善应用程序的功能和性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值