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_Import
obtenga un objeto de función de eso usando PyObject_GetAttrString
y 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
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 esNone
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í usandoPyObject_GetAttrString
y luego llamarlo. También es posible que desee comprobar el valor de retorno dePyRun_String
– si esto esNULL
hubo 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
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.
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