How do I prevent a C shared library to print on stdout in python? How do I prevent a C shared library to print on stdout in python? python python

How do I prevent a C shared library to print on stdout in python?


Based on @Yinon Ehrlich's answer. This variant tries to avoid leaking file descriptors:

import osimport sysfrom contextlib import contextmanager@contextmanagerdef stdout_redirected(to=os.devnull):    '''    import os    with stdout_redirected(to=filename):        print("from Python")        os.system("echo non-Python applications are also supported")    '''    fd = sys.stdout.fileno()    ##### assert that Python and C stdio write using the same file descriptor    ####assert libc.fileno(ctypes.c_void_p.in_dll(libc, "stdout")) == fd == 1    def _redirect_stdout(to):        sys.stdout.close() # + implicit flush()        os.dup2(to.fileno(), fd) # fd writes to 'to' file        sys.stdout = os.fdopen(fd, 'w') # Python writes to fd    with os.fdopen(os.dup(fd), 'w') as old_stdout:        with open(to, 'w') as file:            _redirect_stdout(to=file)        try:            yield # allow code to be run with the redirected stdout        finally:            _redirect_stdout(to=old_stdout) # restore stdout.                                            # buffering and flags such as                                            # CLOEXEC may be different


Yeah, you really want to use os.dup2 instead of os.dup, like your second idea. Your code looks somewhat roundabout. Don't muck about with /dev entries except for /dev/null, it's unnecessary. It's also unnecessary to write anything in C here.

The trick is to save the stdout fdes using dup, then pass it to fdopen to make the new sys.stdout Python object. Meanwhile, open an fdes to /dev/null and use dup2 to overwrite the existing stdout fdes. Then close the old fdes to /dev/null. The call to dup2 is necessary because we can't tell open which fdes we want it to return, dup2 is really the only way to do that.

Edit: And if you're redirecting to a file, then stdout is not line-buffered, so you have to flush it. You can do that from Python and it will interoperate with C correctly. Of course, if you call this function before you ever write anything to stdout, then it doesn't matter.

Here is an example that I just tested that works on my system.

import zookimport osimport sysdef redirect_stdout():    print "Redirecting stdout"    sys.stdout.flush() # <--- important when redirecting to files    newstdout = os.dup(1)    devnull = os.open(os.devnull, os.O_WRONLY)    os.dup2(devnull, 1)    os.close(devnull)    sys.stdout = os.fdopen(newstdout, 'w')zook.myfunc()redirect_stdout()zook.myfunc()print "But python can still print to stdout..."

The "zook" module is a very simple library in C.

#include <Python.h>#include <stdio.h>static PyObject *myfunc(PyObject *self, PyObject *args){    puts("myfunc called");    Py_INCREF(Py_None);    return Py_None;}static PyMethodDef zookMethods[] = {    {"myfunc",  myfunc, METH_VARARGS, "Print a string."},    {NULL, NULL, 0, NULL}};PyMODINIT_FUNCinitzook(void){    (void)Py_InitModule("zook", zookMethods);}

And the output?

$ python2.5 test.pymyfunc calledRedirecting stdoutBut python can still print to stdout...

And redirecting to files?

$ python2.5 test.py > test.txt$ cat test.txtmyfunc calledRedirecting stdoutBut python can still print to stdout...


Combining both answers - https://stackoverflow.com/a/5103455/1820106 & https://stackoverflow.com/a/4178672/1820106 to context manager that blocks print to stdout only for its scope (the code in the first answer blocked any external output, the latter answer missed the sys.stdout.flush() at end):

class HideOutput(object):    '''    A context manager that block stdout for its scope, usage:    with HideOutput():        os.system('ls -l')    '''    def __init__(self, *args, **kw):        sys.stdout.flush()        self._origstdout = sys.stdout        self._oldstdout_fno = os.dup(sys.stdout.fileno())        self._devnull = os.open(os.devnull, os.O_WRONLY)    def __enter__(self):        self._newstdout = os.dup(1)        os.dup2(self._devnull, 1)        os.close(self._devnull)        sys.stdout = os.fdopen(self._newstdout, 'w')    def __exit__(self, exc_type, exc_val, exc_tb):        sys.stdout = self._origstdout        sys.stdout.flush()        os.dup2(self._oldstdout_fno, 1)