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)