Cree y llame a la función python desde una cadena a través de la API de C

7 minutos de lectura

avatar de usuario de alanc10n
alanc10n

¿Es posible cargar una función de python desde una cadena y luego llamar a esa función con argumentos y obtener el valor de retorno?

Estoy usando la API de python C para ejecutar el código de python desde dentro de mi aplicación C++. Puedo cargar un módulo desde un archivo usando PyImport_Importobtenga un objeto de función de eso usando PyObject_GetAttrStringy llamar a la función con PyObject_CallObject. Lo que me gustaría hacer es cargar el módulo/función desde una cadena en lugar de un archivo. ¿Hay algún equivalente a PyImport_Import ¿Qué me permitiría pasarle una cadena en lugar de un archivo? Necesito pasar argumentos a la función a la que llamo y necesito acceso al valor devuelto, por lo que no puedo simplemente usar PyRun_SimpleString.


Editar:

Encontré esta solución después de encenderme PyRun_String. Estoy creando un nuevo módulo, obteniendo su objeto de diccionario, pasándolo en una llamada a PyRun_String para definir una función en mi nuevo módulo, luego obtener un objeto de función para esa función recién creada y llamarlo a través de PyObject_CallObject, pasando mis argumentos. Esto es lo que he encontrado para resolver mi problema:
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;
}

Aquí está el resto de mi publicación original, dejada para la posteridad:

Esto es lo que estaba haciendo originalmente:
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;
}

Y atest.py:


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

  • No es necesario dejar tu publicación para la posteridad. Stack Overflow ya tiene eso cubierto. Todas las ediciones de preguntas y respuestas se archivan automáticamente, lo que podemos ver una vez que nuestros puntos alcanzan cierto nivel. Eso nos ayuda a revertir las ediciones incorrectas y rastrear los cambios para comprender mejor las preguntas. Entonces, en lugar de dejarlo allí, puede eliminar sus cambios y podemos ver el registro de edición si es necesario. Por eso, ni siquiera es necesario poner “Editar:”.

    – el hombre de hojalata

    14/01/2015 a las 20:02


  • Para otros que tropiezan con esto: en el primer fragmento de código, Py_Initialize se llama demasiado tarde. solo que tu sabes

    – Hola Mundo

    3 abr 2018 a las 16:25


  • Compruebe si usa pValue dos veces, la primera referencia debe provocar una pérdida de memoria

    – themadmax

    8 de agosto de 2018 a las 9:33

  • una pregunta: la función definida en una sola invocación de PyRun_String, ¿tiene que estar completamente contenida en una sola invocación? es decir, el cuerpo de la función no se puede dividir en invocaciones sucesivas de PyRun_String?

    – usuario3181125

    4 de noviembre a las 18:16

PyRun_String en la API de Python C es probablemente lo que está buscando. Ver: http://docs.python.org/c-api/veryhigh.html

  • No está claro cómo pasaría los argumentos a la función. Parece que puedo definir una función usando PyRun_String, pero no veo cómo llamarlo con parámetros que no son solo una cadena codificada. Es decir, podría volver a llamar a PyRun_String con “doStuff(7)” como primer argumento, pero eso no funcionará si el argumento es un objeto o algo así. ¿Hay alguna forma de llamar a PyObject_GetAttrString para obtener el objeto de función para doStuff si se definió a través de PyRun_String?

    – alanc10n

    24 de septiembre de 2010 a las 20:57

  • Aceptar porque me inició en el camino para encontrar una solución, aunque tuve que hacer un lote de lectura para llegar allí. Gracias.

    – alanc10n

    24/09/2010 a las 21:55

  • Lo siento, no estuve en línea durante las últimas horas, pero parece que lo averiguaste exactamente. PyRun_String ejecuta un bloque de código de Python desde una cadena y devuelve lo que el código hubiera devuelto; supongo que es None en tu caso. Después de eso, la función que acaba de evaluar se agrega al espacio de nombres local; debe recuperarla desde allí usando PyObject_GetAttrString y luego llamarlo. También es posible que desee comprobar el valor de retorno de PyRun_String – si esto es NULLhubo una excepción al ejecutar el bloque de código.

    – Tamas

    24 de septiembre de 2010 a las 22:33

La respuesta contenida en la pregunta es excelente, pero tuve algunos pequeños problemas al usarla con Pitón 3.5así que para evitar que alguien más haga lo que hice, a continuación hay una versión ligeramente editada que parece funcionar bien para esta versión de Python al menos:

#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;
}

  • Excelente respuesta! Muy útil para mí. Usé muchas partes de este código en mi programa.

    – Arty

    13 de enero de 2021 a las 17:53


avatar de usuario de frigorifico
frigorifico

Esta pregunta es bastante antigua, sin embargo, si alguien todavía está tratando de resolver esto (como yo), le daré mi respuesta.

La respuesta original y la respuesta actualizada de @kmp funcionan para ejemplos simples, sin embargo, cuando algo como un import declaración se introduce en el código esto no funciona.

Encontré un ejemplo de trabajo aquí: https://awasu.com/weblog/embedding-python/write-ac-wrapper-library-part3/

En resumen (usando Python 3.6 api):

  • Lee un archivo python en:
std::string line, text;
std::ifstream in("the_file.py");
while (std::getline(in, line)) {
  text += line + "\n";
}
const char *data = text.c_str();
  • Compílelo a bytecode:
PyObject *pCodeObj = Py_CompileString(data, "", Py_file_input);
  • Cree un módulo ejecutando el código:
pModule = PyImport_ExecCodeModule("the_module", pCodeObj );

Luego puede continuar obteniendo funciones usando PyObject_GetAttrString y llamándolos como en los ejemplos anteriores.

¿Ha sido útil esta solución?