كيفية اصطياد Python Stdout في C ++ Code
-
29-09-2019 - |
سؤال
لديّ برنامج يحتاج أحيانًا إلى استدعاء Python من أجل تكوين بعض المهام. أحتاج إلى وظيفة تستدعي بيثون و يمسك الثعبان stdout ويضعه في بعض الملفات. هذا إعلان عن الوظيفة
pythonCallBackFunc(const char* pythonInput)
مشكلتي هي التقاط كل الإخراج Python لأمر معين (Pythoninput). ليس لدي أي خبرة مع Python API ولا أعرف ما هي الأسلوب المناسب للقيام بذلك. أول شيء جربته هو إعادة توجيه sdtout من Python و STDERR باستخدام 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)
ربما يكون من المهم ، أنا أستخدم Visual Studio 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;
}
الإخراج الذي أحصل عليه بعد التشغيل الرئيسي:
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 (الإخراج) يرجع سلسلة فارغة (char*) بدلاً من 6 ... :( هل هناك شيء لا أستطيع أن أفقد هذا انتاج؟
ثاكس ، أليكس
المحلول
إذا كنت أقرأ سؤالك بشكل صحيح ، فأنت تريد التقاط 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;
}
نصائح أخرى
هنا حل ودود C ++ لقد طورته مؤخرًا.
أشرح بعض التفاصيل عنها على مدونتي: 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 Lambda.
لاحظ أن هذا مثال أدنى لتقديم المفهوم الأساسي. في رمز جاهز للإنتاج ، يحتاج بالتأكيد إلى مزيد من الاهتمام حول العد المرجعي لـ PyObject
, والتخلص من الدولة العالمية ، وهلم جرا.
أعلم أن هذا السؤال قديم ، ولكن لم يتم الإجابة على جزء واحد من السؤال بعد:
"كيفية التقاط إخراج الأوامر التي لا تكتب مباشرة إلى stdout of Python ، مثل: 1+1؟"
فيما يلي الخطوات (لـ Python 3.4):
إعادة توجيه stdout/stderr إلى متغير python باستخدام حل Mark: https://stackoverflow.com/a/430737/1046299
وظيفة نسخ
PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags)
من كود المصدر بيثون. يقع في الملفpythonrun.c
تعديل
PyRun_InteractiveOneObject
اسم الوظيفة والتوقيع بحيث تأخذ الوظيفة الجديدة ملفconst char*
(أمرك) كمعلمة أولى بدلاً من أFILE*
. ثم ستحتاج إلى استخدامPyParser_ASTFromStringObject
بدلاً منPyParser_ASTFromFileObject
في تنفيذ الوظيفة. لاحظ أنك ستحتاج إلى نسخ الوظيفةrun_mod
كما هي من Python لأنه يسمى في الوظيفة.اتصل بالوظيفة الجديدة مع أمرك ، على سبيل المثال
1+1
. يجب أن تتلقى stdout الآن الإخراج2
.