4

This is a combination of my two recent questions:
[1] Python instance method in C
[2] How to redirect stderr in Python?

I would like to log the output of both stdout and stderr from a python script.

The thing I want to ask is, to create a new type according to [1] seems fairly complicated. Does it simplifies the things if there was no need to expose the new type to Python, i.e. it would only exist in C?

I mean, when Python prints something it goes to "Objects/fileobject.c" and there in "PyFile_WriteObject" it check whether it is possible to write to its argument:

writer = PyObject_GetAttrString(f, "write");
if (writer == NULL)
...

Also, it is possible to get stdout and stderr like this:

PyObject* out = PySys_GetObject("stdout");
PyObject* err = PySys_GetObject("stderr");

My question is then, is it somehow possible to construct necessary PyObject which satisfies the above 'PyObject_GetAttrString(f, "write")' and is callable so I can write:

PySys_SetObject("stdout", <my writer object / class / type / ?>);

http://docs.python.org/c-api/sys.html?highlight=pysys_setobject#PySys_SetObject

This way, there would be no need to expose the new "writer type" to the rest of Python script so I thought it might be a bit simpler to write the code...?

Community
  • 1
  • 1
EcirH
  • 473
  • 1
  • 5
  • 9
  • -1: Combining recent questions. What was wrong with the answers you already got? – S.Lott Dec 24 '09 at 07:25
  • I have just proposed a solution based on C/C++ callback as answer to SO question: [How To catch python stdout in c++ code](http://stackoverflow.com/a/8335297/151641) In that solution, I'm focused on C++, so I'm interested in catching `sys.stdout.write` output with any kind of callable C++ endity: free function, class member function, named function objects or even anonymous functions as in the example above where I use C++11 lambda. – mloskot Dec 01 '11 at 01:01

2 Answers2

12

Just make a module object (you're doing that anyway, if you're using the C API!-) and make it have a suitable write function -- that module object will be suitable as the second argument to PySys_SetObject.

In my answer to your other question I pointed you to xxmodule.c, an example file in Python's C sources, which is a module with a lot of examples including types and functions of various kinds -- you can work from there even if (mysteriously to me) you consider the "make a new type" part too difficult;-).

Edit: here's a trivial working example (aview.py):

#include "Python.h"
#include <stdio.h>

static PyObject *
aview_write(PyObject *self, PyObject *args)
{
    const char *what;
    if (!PyArg_ParseTuple(args, "s", &what))
        return NULL;
    printf("==%s==", what);
    return Py_BuildValue("");
}

static PyMethodDef a_methods[] = {
    {"write", aview_write, METH_VARARGS, "Write something."},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
initaview(void)
{
    PyObject *m = Py_InitModule("aview", a_methods);
    if (m == NULL) return;
    PySys_SetObject("stdout", m);
}

Once this aview module is properly installed:

$ python
Python 2.5.4 (r254:67917, Dec 23 2008, 14:57:27) 
[GCC 4.0.1 (Apple Computer, Inc. build 5363)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import aview
>>> print 'ciao'
==ciao====
==>>> 

...any string emitted to standard output is written with == signs around it (and this print calls .write twice: with 'ciao', and then again with a newline).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • You mean: `static PyMethodDef LogMethods[] = {{"write", console_write, METH_VARARGS, "Writes into console."},...}`? And then: `Py_InitModule("log", LogMethods);` Because I tried that and it didn't work... But maybe I misunderstood you. Ad your `xxmodule.c` - I read it, I really tried, this is totally new to me..... – EcirH Dec 24 '09 at 02:57
  • @EcirH, OK, edited my answer to show a complete if trivial working example -- it works perfectly well (I've tried with Python 2.5 since you didn't mention what you're using, but it should work just about the same in any reasonably modern Python). So what's so complicated in these 20 lines or so of code, that's giving you such anguish?! – Alex Martelli Dec 24 '09 at 03:17
  • Yes, it works perfectly! Thanks a lot! I was missing that `PySys_SetObject("stdout", m);` line... And no, these 20 LOC are exactly what I wanted, this is much easier to understand than `xxmodule.c`. Thank you very much, once more! – EcirH Dec 24 '09 at 03:23
8

Based on Alex's answer, here is fully working C code, without the Python "import aview", in Python 3 (so no Py_InitModule), and with stderr redirection :

#include <functional>
#include <iostream>
#include <string>
#include <Python.h>


PyObject* aview_write(PyObject* self, PyObject* args)
{
    const char *what;
    if (!PyArg_ParseTuple(args, "s", &what))
        return NULL;
    printf("==%s==", what);
    return Py_BuildValue("");
}


PyObject* aview_flush(PyObject* self, PyObject* args)
{
    return Py_BuildValue("");
}


PyMethodDef aview_methods[] =
{
    {"write", aview_write, METH_VARARGS, "doc for write"},
    {"flush", aview_flush, METH_VARARGS, "doc for flush"},
    {0, 0, 0, 0} // sentinel
};


PyModuleDef aview_module =
{
    PyModuleDef_HEAD_INIT, // PyModuleDef_Base m_base;
    "aview",               // const char* m_name;
    "doc for aview",       // const char* m_doc;
    -1,                    // Py_ssize_t m_size;
    aview_methods,        // PyMethodDef *m_methods
    //  inquiry m_reload;  traverseproc m_traverse;  inquiry m_clear;  freefunc m_free;
};

PyMODINIT_FUNC PyInit_aview(void) 
{
    PyObject* m = PyModule_Create(&aview_module);
    PySys_SetObject("stdout", m);
    PySys_SetObject("stderr", m);
    return m;
}


int main()
{
    PyImport_AppendInittab("aview", PyInit_aview);
    Py_Initialize();
    PyImport_ImportModule("aview");

    PyRun_SimpleString("print(\'hello to buffer\')");
    PyRun_SimpleString("make a SyntaxException in stderr");

    Py_Finalize();

    return 0;

}

Note, though, that if you plan to have several distinct interpreters, this won't be enough, because aview_write won't be able to know which buffer to append into. You'll need something like that.

Here is an awesome reference on how to add new modules and types, btw.

Calvin1602
  • 9,413
  • 2
  • 44
  • 55