通过 C API 从字符串创建和调用 python 函数

Posted

技术标签:

【中文标题】通过 C API 从字符串创建和调用 python 函数【英文标题】:Create and call python function from string via C API 【发布时间】:2011-04-16 22:26:41 【问题描述】:

是否可以从字符串加载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

【问题讨论】:

没有必要为后代留下您的帖子。 Stack Overflow 已经涵盖了这一点。对问题和答案的所有编辑都会自动存档,一旦我们的分数达到一定水平,我们就可以看到。这有助于我们回滚错误的编辑,并跟踪更改以更好地理解问题。因此,您可以删除您的更改,而不是将其留在那里,如果需要,我们可以查看编辑日志。因此,甚至不需要输入“编辑:”。 对于其他偶然发现此问题的人:在第一个代码 sn-p 中,调用 Py_Initialize 为时已晚。只是你知道 检查是否使用pValue两次第一次引用一定会造成内存泄漏 【参考方案1】:

Python C API 中的PyRun_String 可能是您正在寻找的。见:http://docs.python.org/c-api/veryhigh.html

【讨论】:

不清楚如何将参数传递给函数。看起来我可以使用PyRun_String 定义一个函数,但我不知道如何使用不仅仅是硬编码字符串的参数来调用它。也就是说,我可以使用“doStuff(7)”作为第一个参数再次调用 PyRun_String,但如果参数是对象或类似的东西,这将不起作用。如果 doStuff 是通过 PyRun_String 定义的,有什么方法可以调用 PyObject_GetAttrString 来获取 doStuff 的函数对象? 接受,因为它让我开始了寻找解决方案的道路,尽管我必须阅读大量的资料才能到达那里。谢谢。 抱歉,我最近几个小时没上网,但看起来你已经猜到了。 PyRun_String 从字符串运行 Python 代码块并返回代码将返回的任何内容——我认为在你的情况下它是 None。之后,您刚刚评估的函数被添加到本地命名空间——您必须使用PyObject_GetAttrString 从那里检索它,然后调用它。您可能还想检查 PyRun_String 的返回值 - 如果是 NULL,则执行代码块时出现异常。【参考方案2】:

问题中包含的答案非常好,但我在使用 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;

【讨论】:

优秀的答案!对我非常有用。我在我的程序中使用了这段代码的很多部分。【参考方案3】:

这个问题已经很老了,但是如果有人仍在尝试解决这个问题(就像我一样),我会提供我的答案。

@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 获取函数并按照上述示例调用它们。

【讨论】:

以上是关于通过 C API 从字符串创建和调用 python 函数的主要内容,如果未能解决你的问题,请参考以下文章

从 JWT Token、Python 获取用户名和照片

无法从 Python C API 中内置的模块调用方法

通过 ctypes 从 Python 调用的 C 函数返回不正确的值

如何使用 Chainlink api 调用通过 api 从 json 返回中检索字符串值

如何通过python调用新浪微博的API

使用 python-c-api 调用的 Cython 回调段错误