Как поймать python stdout в коде С++

У меня есть программа, которая во время работы иногда требует вызова python для выполнения некоторых задач. Мне нужна функция, которая вызывает python и перехватывает python stdout и помещает его в какой-то файл. Это объявление функции

  pythonCallBackFunc(const char* pythonInput)

Моя проблема состоит в том, чтобы поймать все выходные данные python для данной команды (pythonInput). У меня нет опыта работы с Python API, и я не знаю, как правильно это сделать. Первое, что я попробовал, это перенаправить sdtout и stderr python с помощью Py_run_SimpleString, это пример кода, который я написал.

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

Есть лучший способ сделать это? Кроме того, по какой-то причине PyRun_SimpleString ничего не делает, когда получает какое-то математическое выражение, например, PyRun_SimpleString("5**3") ничего не печатает (python conlsul выводит результат: 125)

может быть это важно, я использую визуальную студию 2008. Спасибо, Алекс


Изменения, которые я сделал по предложению Марка:

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

Вывод, который я получаю после запуска main:

 Here's the output: 123

 Here's the output:
 Here's the output: 
 Here's the output: 2

Это хорошо для меня, но только одна проблема, это должно быть

 Here's the output: 123

 Here's the output: 6

 Here's the output: 
 Here's the output: 2

Я не знаю почему, но после запуска этой команды: PythonPrinting("1+5"), команда PyString_AsString(output) возвращает пустую строку (char*) вместо 6... :( Что я могу сделать, чтобы не потерять это? выход?

Такс, Алекс


person alexpov    schedule 29.11.2010    source источник
comment
Вопросы по программированию относятся к StackOverflow.   -  person    schedule 29.11.2010


Ответы (3)


Если я правильно понимаю ваш вопрос, вы хотите записать stdout/stderr в переменную в вашем C++? Вы можете сделать это, перенаправив stdout/stderr в переменную Python, а затем запросив эту переменную в свой C++. Обратите внимание, что я не сделал правильный подсчет ссылок ниже:

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

}
person Mark    schedule 29.11.2010
comment
Привет, Марк, спасибо, это очень полезно. Не могли бы вы объяснить несколько вещей. Во-первых, как работает катетер, во-вторых, я разместил в своем вопросе изменения, которые я сделал в соответствии с вашим предложением. Когда я запускаю вторую команду в main (PythonPrinting(1+5);), функция PyString_AsString(output) возвращает пустую строку, то есть я теряю исходный вывод python, а именно: 6. Какие изменения я не могу сделать, чтобы потерять это? Еще раз спасибо... :) - person alexpov; 30.11.2010
comment
@alexpov, ловушка работает, просто перенаправляя stdout и stderr Python в переменную. Вы не видите никакого вывода для 1 + 5, потому что python ничего не отправляет в стандартный вывод для этого случая. Вы должны использовать печать (1 + 5). Кроме того, рефакторинг вашего кода, вы не должны делать несколько вызовов PyImport_AddModule - person Mark; 01.12.2010
comment
привет, в моем случае я просто запускаю команды python из c, и все, что мне нужно, это способ захватить все выходные данные python. Я не знаю, какая это будет команда, команда, которая заставляет python печатать в sdtout или stderr, или команда 1+1. Я не могу обернуть все свои команды в print. Знаете ли вы способ сделать перенаправление (или что-то еще), чтобы я мог перехватить эти выходные данные? (куда python отправляет этот вывод?) О PyImport_AddModule, когда я вызываю его один раз, значение ловушки содержит все предыдущие выходные данные. Как мне преобразовать это значение в пустую строку после каждого вызова PythonPrinting? Еще раз спасибо, Алекс - person alexpov; 01.12.2010
comment
Не забудьте сначала убедиться, что это объект PyString с помощью: if (PyObject_IsInstance(output, (PyObject *)&PyString_Type)) { и освободить память с помощью: Py_DecRef(output);. - person chown; 26.11.2011
comment
обновление: вы также должны реализовать flush(). есть ли способ переопределить только метод записи? - person Leo; 11.08.2015
comment
Я не очень хорошо знаком с Python3.x. Но для Python3 мне пришлось заменить PyString_AsString() на PyBytes_AsString(). Правильный? - person G.Vanem; 20.05.2018

Вот дружественное к С++ решение, которое я разработал в последнее время.

Я объясняю некоторые подробности в своем блоге: Перенаправление Python sys.stdout в C++, где я также указываю на репозиторий на моем GitHub, где можно найти самую последнюю версию. Вот полный пример, основанный на текущем коде на момент публикации этого ответа:

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

Это позволяет перехватывать вывод sys.stdout.write с любой вызываемой сущностью C++: свободной функцией, функцией-членом класса, именованными функциональными объектами или даже анонимными функциями, как в приведенном выше примере, где я использую лямбда C++11.

Обратите внимание, что это минимальный пример для представления основной концепции. В готовом к работе коде, безусловно, требуется больше внимания к подсчету ссылок PyObject, избавлению от глобального состояния и так далее.

person mloskot    schedule 01.12.2011
comment
Работает как шарм. Было бы даже идеально, если бы также перенаправить stderr на другую функцию.... ;-) - person jpo38; 08.10.2018
comment
@ jpo38 Рад это знать. Думаю, я сделал пример как можно короче, оставив читателю любые улучшения. Добиться того, что вы предлагаете, должно быть не так уж сложно :) - person mloskot; 08.10.2018
comment
@mloskot: Конечно, я расширил его, чтобы перенаправить оба. Это был просто бесплатный комментарий. Спасибо за этот код, он действительно помог! - person jpo38; 08.10.2018
comment
Arg, это фактически терпит неудачу, когда сообщение имеет кодировку utf-8. Затем строки обрезаются... - person jpo38; 16.10.2020

Я знаю, что этот вопрос старый, но на одну часть вопроса еще не ответили:

«Как перехватить вывод команд, которые не записывают напрямую в стандартный вывод Python, например: 1+1?»

Вот шаги (для Python 3.4):

  1. Перенаправьте stdout/stderr в переменную Python, используя решение Марка: https://stackoverflow.com/a/4307737/1046299

  2. Скопируйте функцию PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) из исходного кода Python. Он находится в файле pythonrun.c

  3. Измените имя и сигнатуру функции PyRun_InteractiveOneObject, чтобы новая функция принимала const char* (ваша команда) в качестве первого параметра вместо FILE*. Тогда вам нужно будет использовать PyParser_ASTFromStringObject вместо PyParser_ASTFromFileObject в реализации функции. Обратите внимание, что вам нужно будет скопировать функцию run_mod как есть из Python, поскольку она вызывается внутри функции.

  4. Вызовите новую функцию своей командой, например 1+1. Теперь стандартный вывод должен получить вывод 2.

person MasterMind    schedule 04.09.2014