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 os
import sys
from contextlib import contextmanager
@contextmanager
def 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 zook
import os
import sys
def 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_FUNC
initzook(void)
{
(void)Py_InitModule("zook", zookMethods);
}
And the output?
$ python2.5 test.py
myfunc called
Redirecting stdout
But python can still print to stdout...
And redirecting to files?
$ python2.5 test.py > test.txt
$ cat test.txt
myfunc called
Redirecting stdout
But 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)