2010-11-29 22 views
21

J'ai un programme qui lors de son exécution doit parfois appeler python afin de préformer certaines tâches. J'ai besoin d'une fonction qui appelle python et attrape pythons stdout et le met dans un fichier. Ceci est une déclaration de la fonctionComment faire pour attraper python stdout dans le code C++

pythonCallBackFunc(const char* pythonInput) 

Mon problème est d'attraper toute la sortie de python pour une commande donnée (pythonInput). Je n'ai aucune expérience avec API python et je ne sais pas quelle est la bonne technique pour le faire. La première chose que j'ai essayée est de rediriger le sdtout et le stderr de Python using Py_run_SimpleString ceci est un exemple du code que j'ai écrit.

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

Y a-t-il une meilleure façon de procéder? En outre, pour une raison quelconque PyRun_SimpleString ne fait rien lorsqu'il obtient une expression mathématique, par exemple PyRun_SimpleString ("5 ** 3") n'imprime rien (python conlsul affiche le résultat: 125)

peut-être que c'est important, j'utilise visuel studio 2008. Merci, Alex


changements que j'ai fait selon la suggestion 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 sortie je reçois après l'exécution principale:

Here's the output: 123 

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

Il est bon pour moi, mais un seul problème, il devrait être

Here's the output: 123 

Here's the output: 6 

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

Je ne sais pas pourquoi, mais après l'exécution de cette commande: PythonPrinting ("1 + 5"), PyString_AsString (sortie) La commande retourne une chaîne vide (char *) au lieu de 6 ... :(Y at-il quelque chose que je puisse faire pour ne pas perdre cette sortie?

Thaks, Alex

+0

Les questions de programmation appartiennent à StackOverflow. –

Répondre

16

Si je lis bien votre question, vous voulez capturer stdout/stderr dans une variable au sein de votre C++? Vous pouvez le faire en redirigeant stdout/stderr dans une variable python, puis en interrogeant cette variable dans votre C++. S'il vous plaît pas que je ne l'ai pas fait l'arbitre bon comptage ci-dessous:

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

} 
+0

Bonjour marque, merci, c'est très utile. Pouvez-vous s'il vous plaît expliquer plusieurs choses. Tout d'abord, comment fonctionne la cather, en second lieu, j'ai posté sur ma question les changements que j'ai faits selon votre suggestion. Lorsque j'exécute la deuxième commande dans main (PythonPrinting ("1 + 5");), la fonction PyString_AsString (output) renvoie une chaîne vide, meanin, je perds la sortie python d'origine qui est: 6. Quels changements peuvent je ne perds pas ça? Merci encore ... :) – alexpov

+1

@alexpov, le receveur fonctionne en redirigeant simplement les stdout et stderr de Python vers une variable. Vous ne voyez aucune sortie pour "1 + 5" car python n'envoie rien à stdout dans ce cas. Vous devriez utiliser "print (1 + 5)". De plus, refactorisez votre code, vous ne devriez pas faire plusieurs appels au bonjour PyImport_AddModule – Mark

+0

, dans mon cas, je lance simplement des commandes python depuis c et tout ce dont j'ai besoin est un moyen de capturer toutes les sorties pythons. Je ne sais pas quelle commande ce sera, une commande qui fait imprimer python à sdtout ou stderr ou une commande "1 + 1". Je ne peux pas emballer toutes mes commandes avec l'impression. Savez-vous un moyen de faire une redirection (ou quelque chose d'autre) afin que je puisse attraper ces sorties? (où python envoie-t-il cette sortie?) A propos de PyImport_AddModule, quand je l'appelle une fois, le catcher "value" contient toutes les sorties précédentes. Comment dois-je initialiser cette valeur pour vider la chaîne après chaque appel de PythonPrinting? Merci encore, Alex – alexpov

24

Voici un C++ solution à l'amiable, j'ai développé ces derniers temps.

J'en explique quelques détails sur mon blog: Python sys.stdout redirection in C++ où je pointe également vers le référentiel de mon GitHub où la version la plus récente peut être trouvée. Voici exemple complet basé sur le code en vigueur au moment de l'affichage de cette réponse:

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

Cela permet d'intercepter sys.stdout.write sortie avec tout type de appelable C++ entité: Fonction libre, fonction de membre de classe, objets fonction nommée ou fonctions même anonymes comme dans l'exemple ci-dessus où j'utilise C++11 lambda. Notez qu'il s'agit d'un exemple minimal pour présenter le concept essentiel. Dans le code de production-prêt, il a certainement besoin de plus d'attention autour du comptage de référence de PyObject, en se débarrassant de l'état global, et ainsi de suite.

+1

C'est impressionnant. –

3

Je sais que cette question est ancienne, mais une partie de la question n'a pas encore reçu de réponse:

« Comment attraper la sortie des commandes qui ne pas écrire directement à la sortie standard de Python, comme: 1 + 1? "

Voici les étapes (pour Python 3.4):

  1. Rediriger stdout/stderr dans une variable Python en utilisant la solution de Mark: https://stackoverflow.com/a/4307737/1046299

  2. Fonction copie PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags) à partir du code source Python. Il est situé dans le fichier pythonrun.c

  3. Modifier le nom de la fonction PyRun_InteractiveOneObject et signature de telle sorte que la nouvelle fonction prend une const char* (votre commande) en tant que premier paramètre au lieu d'un FILE*. Ensuite, vous devrez utiliser PyParser_ASTFromStringObject au lieu de PyParser_ASTFromFileObject dans l'implémentation de la fonction. Notez que vous devrez copier la fonction run_modainsi que à partir de Python car elle est appelée dans la fonction.

  4. Appelez la nouvelle fonction avec votre commande, par exemple 1+1. Stdout devrait maintenant recevoir la sortie 2.

+0

en effet vieux un :) mais merci – alexpov