通过C API从字符串创建并调用Python函数

19 投票
3 回答
13203 浏览
提问于 2025-04-16 04:34

有没有办法从一个字符串加载一个Python函数,然后用参数调用这个函数并获取返回值呢?

我正在使用Python的C API在我的C++应用程序中运行Python代码。我可以通过PyImport_Import从文件中加载一个模块,然后用PyObject_GetAttrString从中获取一个函数对象,再用PyObject_CallObject调用这个函数。我想做的是从一个字符串中加载模块或函数,而不是从文件中。有没有类似于PyImport_Import的东西,可以让我传入一个字符串而不是文件?我需要给我调用的函数传递参数,并且需要获取返回值,所以我不能只用PyRun_SimpleString


编辑:

在了解到PyRun_String后,我找到了这个解决方案。我创建了一个新模块,获取它的字典对象,然后在调用PyRun_String时传入这个字典对象,以在我的新模块中定义一个函数,接着获取这个新创建的函数的函数对象,并通过PyObject_CallObject调用它,传入我的参数。这是我找到的解决我问题的方法:

main.cpp


int main()
{
    PyObject *pName, *pModule, *pArgs, *pValue, *pFunc;
    PyObject *pGlobal = PyDict_New();
    PyObject *pLocal;

    //Create a new module object
    PyObject *pNewMod = PyModule_New("mymod");

    Py_Initialize();
    PyModule_AddStringConstant(pNewMod, "__file__", "");

    //Get the dictionary object from my module so I can pass this to PyRun_String
    pLocal = PyModule_GetDict(pNewMod);

    //Define my function in the newly created module
    pValue = PyRun_String("def blah(x):\n\tprint 5 * x\n\treturn 77\n", Py_file_input, pGlobal, pLocal);
    Py_DECREF(pValue);

    //Get a pointer to the function I just defined
    pFunc = PyObject_GetAttrString(pNewMod, "blah");

    //Build a tuple to hold my arguments (just the number 4 in this case)
    pArgs = PyTuple_New(1);
    pValue = PyInt_FromLong(4);
    PyTuple_SetItem(pArgs, 0, pValue);

    //Call my function, passing it the number four
    pValue = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    printf("Returned val: %ld\n", PyInt_AsLong(pValue));
    Py_DECREF(pValue);

    Py_XDECREF(pFunc);
    Py_DECREF(pNewMod);
    Py_Finalize();

    return 0;
}

以下是我原帖的其余部分,留作纪念:

这是我最初的做法:

main.cpp


#include <Python.h>

int main()
{
    PyObject *pName, *pModule, *pArgs, *pValue, *pFunc;

    Py_Initialize();
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('')");
    pName = PyString_FromString("atest");
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if(pModule == NULL)
    {
        printf("PMod is null\n");
        PyErr_Print();
        return 1;
    }

    pFunc = PyObject_GetAttrString(pModule, "doStuff");
    pArgs = PyTuple_New(1);
    pValue = PyInt_FromLong(4);
    PyTuple_SetItem(pArgs, 0, pValue);

    pValue = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    printf("Returned val: %ld\n", PyInt_AsLong(pValue));
    Py_DECREF(pValue);

    Py_XDECREF(pFunc);
    Py_DECREF(pModule);

    Py_Finalize();

    return 0;
}

还有atest.py


def doStuff( x):
    print "X is %d\n" % x
    return 2 * x

3 个回答

4

这个问题虽然有点老,但如果还有人像我一样在研究这个,我就分享一下我的答案。

最初的答案和@kmp更新的答案在简单的例子中是有效的,但一旦代码中引入了像import这样的语句,就不再适用了。

我在这里找到一个有效的例子:https://awasu.com/weblog/embedding-python/writing-a-c-wrapper-library-part3/

简单来说(使用Python 3.6的API):

  • 先读取一个Python文件:
std::string line, text;
std::ifstream in("the_file.py");
while (std::getline(in, line)) {
  text += line + "\n";
}
const char *data = text.c_str();
  • 然后把它编译成字节码:
PyObject *pCodeObj = Py_CompileString(data, "", Py_file_input);
  • 接着通过执行代码来创建一个模块:
pModule = PyImport_ExecCodeModule("the_module", pCodeObj );

之后你可以继续使用PyObject_GetAttrString来获取函数,并像上面的例子那样调用它们。

7

问题中的答案非常好,但我在使用它的时候遇到了一些小问题,特别是在Python 3.5上。为了避免其他人也遇到我遇到的麻烦,下面是一个稍微修改过的版本,至少在这个版本的Python上运行得很好:

#include <Python.h>

int main(void)
{
    PyObject *pArgs, *pValue, *pFunc, *pModule, *pGlobal, *pLocal;

    Py_Initialize();

    pGlobal = PyDict_New();

    //Create a new module object
    pModule = PyModule_New("mymod");
    PyModule_AddStringConstant(pModule, "__file__", "");

    //Get the dictionary object from my module so I can pass this to PyRun_String
    pLocal = PyModule_GetDict(pModule);

    //Define my function in the newly created module
    pValue = PyRun_String("def blah(x):\n\ty = x * 5\n\treturn y\n",
        Py_file_input, pGlobal, pLocal);

    //pValue would be null if the Python syntax is wrong, for example
    if (pValue == NULL) {
        if (PyErr_Occurred()) {
            PyErr_Print();
        }
        return 1;
    }

    //pValue is the result of the executing code, 
    //chuck it away because we've only declared a function
    Py_DECREF(pValue);

    //Get a pointer to the function I just defined
    pFunc = PyObject_GetAttrString(pModule, "blah");

    //Double check we have actually found it and it is callable
    if (!pFunc || !PyCallable_Check(pFunc)) {
        if (PyErr_Occurred()) {
            PyErr_Print();
        }
        fprintf(stderr, "Cannot find function \"blah\"\n");
        return 2;
    }

    //Build a tuple to hold my arguments (just the number 4 in this case)
    pArgs = PyTuple_New(1);
    pValue = PyLong_FromLong(4);
    PyTuple_SetItem(pArgs, 0, pValue);

    //Call my function, passing it the number four
    pValue = PyObject_CallObject(pFunc, pArgs);

    fprintf(stdout, "Returned value: %ld\n", PyLong_AsLong(pValue));

    Py_DECREF(pValue);
    Py_XDECREF(pFunc);

    Py_Finalize();

    return 0;
}
8

在Python的C语言接口中,PyRun_String可能正是你需要的东西。你可以查看这个链接了解更多信息:http://docs.python.org/c-api/veryhigh.html

撰写回答