从C/C++调用Python方法并提取其返回值

90
我想从C语言中调用在Python模块中定义的自定义函数。我有一些初步的代码来实现这个目标,但它只是将输出打印到标准输出流。 mytest.py
import math

def myabs(x):
    return math.fabs(x)

test.cpp

#include <Python.h>

int main() {
    Py_Initialize();
    PyRun_SimpleString("import sys; sys.path.append('.')");
    PyRun_SimpleString("import mytest;");
    PyRun_SimpleString("print mytest.myabs(2.0)");
    Py_Finalize();

    return 0;
}

我该如何将返回值提取为 C double 并在 C 中使用它?


2
你看过这个吗:http://docs.python.org/c-api/?它似乎回答了你的问题。http://docs.python.org/c-api/number.html#PyNumber_Float 似乎是你要找的。有什么问题吗?还需要什么? - S.Lott
1
问题实际上是如何从 mytest.myabs(2.0) 访问返回的对象。一旦我有了指向它的指针,我就可以使用 PyNumber_Float 函数轻松地将其转换为浮点数。 - D R
9
我们能否只给出一个带有代码示例的答案就可以了,不需要再多解释了吗? - Matt Joiner
10个回答

104

如前所述,使用PyRun_SimpleString似乎是个坏主意。

你应该绝对使用C-API提供的方法 (http://docs.python.org/c-api/)。

阅读介绍是理解其工作方式的第一步。

首先,你必须了解PyObject,它是C API的基本对象。它可以表示任何类型的python基本类型(字符串、浮点数、整数等)。

有许多函数可用于将例如python字符串转换为char*或将PyFloat转换为double。

首先,导入你的模块 :

PyObject* myModuleString = PyString_FromString((char*)"mytest");
PyObject* myModule = PyImport_Import(myModuleString);

然后获取您的函数的引用:

PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"myabs");
PyObject* args = PyTuple_Pack(1,PyFloat_FromDouble(2.0));

接着获取你的结果:

PyObject* myResult = PyObject_CallObject(myFunction, args)

再回到一个double类型:

double result = PyFloat_AsDouble(myResult);

你显然应该检查错误(参见Mark Tolonen给出的链接)。

如果你有任何问题,不要犹豫。祝好运。


8
感谢您提供的有用提示,我会接受您的答案。但如果您能够提供一个完整的、调用上述 Python 函数的 C 模块,那对我来说将更加有帮助。 - D R
如果您调用一个对象的类方法,是否需要在参数中传递该对象? - Translunar
14
尽管这个回答已经被接受,但它不是一个调用Python代码的好模板(我在其他地方也看到过引用它的情况)。PyTuple_Pack这行代码每执行一次就会泄漏一个float对象。可以使用result = PyObject_CallFunction(myFunction, "d", 2.0)来避免手动转换和元组创建,并进行函数调用。导入可以写成module = PyImport_ImportModule("mytest")(不需要创建一个Python字符串)。 - user4815162342
我刚刚遇到了一些代码问题,我没有成功导入我的代码,所以需要做的就是添加 PyRun_SimpleString("import sys; sys.path.append('.')"); 然后使用 PyObject 方法。感谢这段代码,现在它可以工作了。 - messy212
1
这适用于调用在类中定义的函数吗? - nice_remark
4
如果您使用的是 Python >3.5 版本,则PyString_FromString()应替换为PyUnicode_FromString() - applemonkey496

32

这是我写的一个示例代码(在各种在线资源的帮助下),用于将一个字符串发送到Python代码中,然后返回一个值。

以下是C代码 call_function.c

#include <Python.h>
#include <stdlib.h>
int main()
{
   // Set PYTHONPATH TO working directory
   setenv("PYTHONPATH",".",1);

   PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *presult;


   // Initialize the Python Interpreter
   Py_Initialize();


   // Build the name object
   pName = PyString_FromString((char*)"arbName");

   // Load the module object
   pModule = PyImport_Import(pName);


   // pDict is a borrowed reference 
   pDict = PyModule_GetDict(pModule);


   // pFunc is also a borrowed reference 
   pFunc = PyDict_GetItemString(pDict, (char*)"someFunction");

   if (PyCallable_Check(pFunc))
   {
       pValue=Py_BuildValue("(z)",(char*)"something");
       PyErr_Print();
       printf("Let's give this a shot!\n");
       presult=PyObject_CallObject(pFunc,pValue);
       PyErr_Print();
   } else 
   {
       PyErr_Print();
   }
   printf("Result is %d\n",PyInt_AsLong(presult));
   Py_DECREF(pValue);

   // Clean up
   Py_DECREF(pModule);
   Py_DECREF(pName);

   // Finish the Python Interpreter
   Py_Finalize();


    return 0;
}

这是Python代码,存储在文件arbName.py中:

 def someFunction(text):
    print 'You passed this Python program '+text+' from C! Congratulations!'
    return 12345

我使用命令 gcc call_function.c -I/usr/include/python2.6 -lpython2.6 ; ./a.out 运行这个程序。我正在使用Redhat。我建议使用PyErr_Print();进行错误检查。


4
使用 ./a.out 而不是 a.out 更好,因为对 a.out 的调用依赖于当前工作目录在 PATH 中,而这并不是一个非常常见的配置。您还应考虑使用 GCC 提供的 -o 选项来为可执行文件指定更好的名称。 - Palec
1
请查看 Meta SE 上的 编辑是如何工作的?。如果您可以进入修订历史记录但无法看到标记中的更改,请将差异视图切换为 并排的 markdown。在修订历史记录中,每个修订版本上方都有切换差异视图的按钮。如果您不了解标记的作用,请参考编辑帮助。如果您正在编辑一篇文章,则编辑器内置帮助 - 查看编辑器右上角的橙色问号。 - Palec
1
这在Python 2.7上给我一个段错误 :( - jhegedus
1
зј–иҜ‘е‘Ҫд»ӨиЎҢпјҲз”ЁдәҺC++пјүпјҡ g++ call_function.cpp python2.7-config --cflags python2.7-config --ldflags -o call_function - andrew
6
在Python3中,将PyString_FromString替换为PyUnicode_FromString,将Py_Finalize替换为Py_FinalizeEx - e-info128
pValue=Py_BuildValue("(z)",(char*)"something"); 中使用 "z" 的原因是它代表了 Python str/null 对象和 C++ char*。对于其他参数类型,请参考 https://docs.python.org/3.6/c-api/arg.html。 - mauriii

11

一个完整的调用Python函数并获取结果的示例可以在http://docs.python.org/release/2.6.5/extending/embedding.html#pure-embedding找到:

#include <Python.h>

int
main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pDict, *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;
}

请查看我的后续问题,链接在这里:https://stackoverflow.com/questions/50441365/calling-python-hello-world-function-from-c-parsing-string-arguments - user13107

6
为了避免其他答案中的额外 .py 文件,您可以直接检索由第一次调用PyRun_SimpleString创建的__main__模块:
PyObject *moduleMainString = PyString_FromString("__main__");
PyObject *moduleMain = PyImport_Import(moduleMainString);

PyRun_SimpleString(
    "def mul(a, b):                                 \n"\
    "   return a * b                                \n"\
);

PyObject *func = PyObject_GetAttrString(moduleMain, "mul");
PyObject *args = PyTuple_Pack(2, PyFloat_FromDouble(3.0), PyFloat_FromDouble(4.0));

PyObject *result = PyObject_CallObject(func, args);

printf("mul(3,4): %.2f\n", PyFloat_AsDouble(result)); // 12

2

正如其他人已经提到的,这在Python文档中有解答。然而,由于我来自Python世界并没有太多使用C/C++的经验,所以我在使用Python 3时遇到了一些问题。因此,在StackOverflow浏览其他帖子花费一些时间后,这里是我的完整工作示例,可以让Python文档运行:

文件c_function.c

#define PY_SSIZE_T_CLEAN
#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();

    // I had to add the following two lines to make it work
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append(\".\")");

    pName = PyUnicode_DecodeFSDefault(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 = PyLong_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", PyLong_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;
    }
    if (Py_FinalizeEx() < 0) {
        return 120;
    }
    return 0;
}

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

我们需要编译和链接它。可以使用以下命令完成:
gcc c_function.c -c $(python3.6-config --cflags) -fPIC

跟随着
gcc c_function.o $(python3.6-config --ldflags) -o call

这是针对Python 3.6的示例。之后,可以通过执行Python文档中的示例来进行操作。

./call multiply multiply 3 2

这太棒了!!!谢谢!// 我必须添加以下两行代码才能使其正常工作 PyRun_SimpleString("import sys"); PyRun_SimpleString("sys.path.append(\".\")"); - velenos14

1

我已经使用BOOST将Python嵌入到C++中完成了它 [这个工作的C模块应该会有所帮助]

#include <boost/python.hpp>

void main()
{
using namespace boost::python;
 Py_Initialize();
 PyObject* filename = PyString_FromString((char*)"memory_leak_test");
     PyObject* imp = PyImport_Import(filename);
     PyObject* func = PyObject_GetAttrString(imp,(char*)"begin");
     PyObject* args = PyTuple_Pack(1,PyString_FromString("CacheSetup"));
     PyObject* retured_value = PyObject_CallObject(func, args); // if you have arg
     double retured_value = PyFloat_AsDouble(myResult);
 std::cout << result << std::endl;
 Py_Finalize();
}

2
这个脚本中的boost部分在哪里? - Antonello

1
你需要以某种方式提取Python方法并使用PyObject_CallObject()运行它。为了做到这一点,可以像扩展和嵌入Python教程中的示例一样为Python提供设置该函数的方式。

1
如果您将返回值分配给变量,那么您可以使用类似于PyEval_GetGlobals()和PyDict_GetItemString()的函数来获取PyObject。从那里,PyNumber_Float可以获取您想要的值。
我建议浏览整个API-当您看到可用的不同方法时,某些事情会变得明显,并且可能会有比我描述的更好的方法。

0

这里是一个最小可执行版本,也适用于Python 3(已在Python 2.7和3.9上进行了测试)。

文档链接包含在注释中,但所有链接都可以在https://docs.python.org/3/c-api/下访问。

#include <Python.h>
#include <stdio.h>

int main()
{
    // Initialize the Python Interpreter
    Py_Initialize();

    // see https://docs.python.org/3/c-api/structures.html
    // NULL objects are special and Py_CLEAR knows this
    PyObject *module = NULL, *result = NULL;

    // https://docs.python.org/3/c-api/import.html
    module = PyImport_ImportModule("mytest");
    if (!module) {
        // Python generally uses exceptions to indicate an error state which
        // gets flagged in the C-API (a NULL pointer in this case) indicating
        // "something" failed. the PyErr_* API should be used to get more
        // details
        goto done;
    }

    // see https://docs.python.org/3/c-api/call.html#c.PyObject_CallMethod
    // and https://docs.python.org/3/c-api/arg.html#building-values
    result = PyObject_CallMethod(module, "myabs", "f", 3.14);
    if (!result) {
        goto done;
    }

    // make sure we got our number back
    if (PyFloat_Check(result)) {
        printf("Successfully got a float: %f\n", PyFloat_AsDouble(result));
    } else {
        printf("Successfully got something unexpected!\n");
    }

  done:
    // see https://docs.python.org/3/c-api/exceptions.html
    PyErr_Print();

    // see https://docs.python.org/3/c-api/refcounting.html
    Py_CLEAR(result);
    Py_CLEAR(module);

    // Optionally release Python Interpreter
    Py_Finalize();

    return 0;
}

这里使用了原帖中的 Python 代码 mytest.py,或者可以使用这个一行等价代码:

from math import fabs as myabs

构建将会依赖于操作系统/Python版本,但以下内容适用于我:

cc -o test -I/usr/include/python3.9 /usr/lib/libpython3.9.so test.c

0

这里是对你的问题的简单直接回答:

    #include <iostream>
    #include <Python.h>
    using namespace std;
    int main()
    {
    const char *scriptDirectoryName = "/yourDir";
    Py_Initialize();
    PyObject *sysPath = PySys_GetObject("path");
    PyObject *path = PyString_FromString(scriptDirectoryName);
    int result = PyList_Insert(sysPath, 0, path);
    PyObject *pModule = PyImport_ImportModule("mytest");

    PyObject* myFunction = PyObject_GetAttrString(pModule,(char*)"myabs");
    PyObject* args = PyTuple_Pack(1,PyFloat_FromDouble(-2.0));


    PyObject* myResult = PyObject_CallObject(myFunction, args);
    double getResult = PyFloat_AsDouble(myResult);
    return 0;
    }

我收到了以下错误信息:“PyString_FromString”在此范围内未声明。 PyObject *path = PyString_FromString(scriptDirectoryName); - fisakhan

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