guia - Cómo atrapar Python stdout en código de C++
qgis manual (3)
Tengo un programa que durante su ejecución a veces necesita llamar a Python para realizar algunas tareas. Necesito una función que llame a python y atrape pythons stdout y lo ponga en algún archivo. Esta es una declaración de la función
pythonCallBackFunc(const char* pythonInput)
Mi problema es capturar toda la salida de python para un comando dado (pythonInput). No tengo experiencia con la API de Python y no sé cuál es la técnica correcta para hacerlo. Lo primero que he intentado es redirigir sdtout y stderr de Python utilizando Py_run_SimpleString, este es un ejemplo del código que he escrito.
#include "boost/python.hpp"
#include <iostream>
void pythonCallBackFunc(const char* inputStr){
PyRun_SimpleString(inputStr);
}
int main () {
...
//S0me outside functions does this
Py_Initialize();
PyRun_SimpleString("import sys");
PyRun_SimpleString("old_stdout = sys.stdout");
PyRun_SimpleString("fsock = open(''python_out.log'',''a'')");
PyRun_SimpleString("sys.stdout = fsock");
...
//my func
pythonCallBackFunc("print ''HAHAHAHAHA''");
pythonCallBackFunc("result = 5");
pythonCallBackFunc("print result");
pythonCallBackFunc("result = ''Hello ''+''World!''");
pythonCallBackFunc("print result");
pythonCallBackFunc("''KUKU ''+''KAKA''");
pythonCallBackFunc("5**3");
pythonCallBackFunc("prinhghult");
pythonCallBackFunc("execfile(''stdout_close.py'')");
...
//Again anothers function code
PyRun_SimpleString("sys.stdout = old_stdout");
PyRun_SimpleString("fsock.close()");
Py_Finalize();
return 0;
}
¿Hay una mejor manera de hacer esto? Además, por alguna razón PyRun_SimpleString no hace nada cuando obtiene una expresión matemática, por ejemplo, PyRun_SimpleString ("5 ** 3") no imprime nada (Python conlsul imprime el resultado: 125)
tal vez es importante, estoy usando Visual Studio 2008. Gracias, Alex
Cambios que hice de acuerdo con la sugerencia de Mark:
#include <python.h>
#include <string>
using namespace std;
void PythonPrinting(string inputStr){
string stdOutErr =
"import sys/n/
class CatchOut:/n/
def __init__(self):/n/
self.value = ''''/n/
def write(self, txt):/n/
self.value += txt/n/
catchOut = CatchOut()/n/
sys.stdout = catchOut/n/
sys.stderr = catchOut/n/
"; //this is python code to redirect stdouts/stderr
PyObject *pModule = PyImport_AddModule("__main__"); //create main module
PyRun_SimpleString(stdOutErr.c_str()); //invoke code to redirect
PyRun_SimpleString(inputStr.c_str());
PyObject *catcher = PyObject_GetAttrString(pModule,"catchOut");
PyObject *output = PyObject_GetAttrString(catcher,"value");
printf("Here''s the output: %s/n", PyString_AsString(output));
}
int main(int argc, char** argv){
Py_Initialize();
PythonPrinting("print 123");
PythonPrinting("1+5");
PythonPrinting("result = 2");
PythonPrinting("print result");
Py_Finalize();
return 0;
}
La salida que obtengo después de ejecutar main:
Here''s the output: 123
Here''s the output:
Here''s the output:
Here''s the output: 2
Es bueno para mí, pero solo un problema, debería ser
Here''s the output: 123
Here''s the output: 6
Here''s the output:
Here''s the output: 2
No sé por qué, pero después de ejecutar este comando: PythonPrinting ("1 + 5"), el comando PyString_AsString (salida) devuelve una cadena vacía (char *) en lugar de 6 ... :( ¿Hay algo que pueda hacer para no perder esto? ¿salida?
Thaks, Alex
Aquí hay una solución amigable con C ++ que he desarrollado últimamente.
Explico algunos detalles de esto en mi blog: Python sys.stdout redirection en C ++ donde también apunto a repositorio en mi GitHub donde se puede encontrar la versión más reciente. Aquí hay un ejemplo completo basado en el código actual en el momento de publicar esta respuesta:
#include <functional>
#include <iostream>
#include <string>
#include <Python.h>
namespace emb
{
typedef std::function<void(std::string)> stdout_write_type;
struct Stdout
{
PyObject_HEAD
stdout_write_type write;
};
PyObject* Stdout_write(PyObject* self, PyObject* args)
{
std::size_t written(0);
Stdout* selfimpl = reinterpret_cast<Stdout*>(self);
if (selfimpl->write)
{
char* data;
if (!PyArg_ParseTuple(args, "s", &data))
return 0;
std::string str(data);
selfimpl->write(str);
written = str.size();
}
return PyLong_FromSize_t(written);
}
PyObject* Stdout_flush(PyObject* self, PyObject* args)
{
// no-op
return Py_BuildValue("");
}
PyMethodDef Stdout_methods[] =
{
{"write", Stdout_write, METH_VARARGS, "sys.stdout.write"},
{"flush", Stdout_flush, METH_VARARGS, "sys.stdout.flush"},
{0, 0, 0, 0} // sentinel
};
PyTypeObject StdoutType =
{
PyVarObject_HEAD_INIT(0, 0)
"emb.StdoutType", /* tp_name */
sizeof(Stdout), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"emb.Stdout objects", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Stdout_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};
PyModuleDef embmodule =
{
PyModuleDef_HEAD_INIT,
"emb", 0, -1, 0,
};
// Internal state
PyObject* g_stdout;
PyObject* g_stdout_saved;
PyMODINIT_FUNC PyInit_emb(void)
{
g_stdout = 0;
g_stdout_saved = 0;
StdoutType.tp_new = PyType_GenericNew;
if (PyType_Ready(&StdoutType) < 0)
return 0;
PyObject* m = PyModule_Create(&embmodule);
if (m)
{
Py_INCREF(&StdoutType);
PyModule_AddObject(m, "Stdout", reinterpret_cast<PyObject*>(&StdoutType));
}
return m;
}
void set_stdout(stdout_write_type write)
{
if (!g_stdout)
{
g_stdout_saved = PySys_GetObject("stdout"); // borrowed
g_stdout = StdoutType.tp_new(&StdoutType, 0, 0);
}
Stdout* impl = reinterpret_cast<Stdout*>(g_stdout);
impl->write = write;
PySys_SetObject("stdout", g_stdout);
}
void reset_stdout()
{
if (g_stdout_saved)
PySys_SetObject("stdout", g_stdout_saved);
Py_XDECREF(g_stdout);
g_stdout = 0;
}
} // namespace emb
int main()
{
PyImport_AppendInittab("emb", emb::PyInit_emb);
Py_Initialize();
PyImport_ImportModule("emb");
PyRun_SimpleString("print(/'hello to console/')");
// here comes the ***magic***
std::string buffer;
{
// switch sys.stdout to custom handler
emb::stdout_write_type write = [&buffer] (std::string s) { buffer += s; };
emb::set_stdout(write);
PyRun_SimpleString("print(/'hello to buffer/')");
PyRun_SimpleString("print(3.14)");
PyRun_SimpleString("print(/'still talking to buffer/')");
emb::reset_stdout();
}
PyRun_SimpleString("print(/'hello to console again/')");
Py_Finalize();
// output what was written to buffer object
std::clog << buffer << std::endl;
}
Esto permite interceptar la salida sys.stdout.write
con cualquier tipo de entidad invocable de C ++: función libre, función miembro de clase, objetos de función nombrados o incluso funciones anónimas como en el ejemplo anterior donde utilizo C ++ 11 lambda .
Tenga en cuenta que este es un ejemplo mínimo para presentar el concepto esencial. En el código de producción lista, sin duda necesita más atención en cuanto al recuento de referencias de PyObject
, deshacerse del estado global, y demás.
Sé que esta pregunta es antigua, pero una parte de la pregunta aún no ha sido respondida:
"Cómo atrapar la salida de comandos que no escriben directamente a la salida estándar de Python, como: 1 + 1?"
Estos son los pasos (para Python 3.4):
Redirija stdout / stderr a una variable de Python con la solución de Mark: https://.com/a/4307737/1046299
Copie la función
PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags)
del código fuente de Python. Está ubicado en el archivopythonrun.c
Modifique el nombre y la firma de la función
PyRun_InteractiveOneObject
para que la nueva función tome unconst char*
(su comando) como primer parámetro en lugar de unFILE*
. Luego necesitará usarPyParser_ASTFromStringObject
lugar dePyParser_ASTFromFileObject
en la implementación de la función. Tenga en cuenta que necesitará copiar la funciónrun_mod
como está de Python, ya que se llama dentro de la función.Llame a la nueva función con su comando, por ejemplo
1+1
. Stdout ahora debería recibir la salida2
.
Si estoy leyendo su pregunta correctamente, ¿desea capturar stdout / stderr en una variable dentro de su C ++? Puede hacerlo redirigiendo stdout / stderr a una variable de python y luego consultando esta variable en su C ++. Por favor, no es que no he hecho el recuento de ref apropiado a continuación:
#include <Python.h>
#include <string>
int main(int argc, char** argv)
{
std::string stdOutErr =
"import sys/n/
class CatchOutErr:/n/
def __init__(self):/n/
self.value = ''''/n/
def write(self, txt):/n/
self.value += txt/n/
catchOutErr = CatchOutErr()/n/
sys.stdout = catchOutErr/n/
sys.stderr = catchOutErr/n/
"; //this is python code to redirect stdouts/stderr
Py_Initialize();
PyObject *pModule = PyImport_AddModule("__main__"); //create main module
PyRun_SimpleString(stdOutErr.c_str()); //invoke code to redirect
PyRun_SimpleString("print(1+1)"); //this is ok stdout
PyRun_SimpleString("1+a"); //this creates an error
PyObject *catcher = PyObject_GetAttrString(pModule,"catchOutErr"); //get our catchOutErr created above
PyErr_Print(); //make python print any errors
PyObject *output = PyObject_GetAttrString(catcher,"value"); //get the stdout and stderr from our catchOutErr object
printf("Here''s the output:/n %s", PyString_AsString(output)); //it''s not in our C++ portion
Py_Finalize();
return 0;
}