Python: Forcibly redirect stdout and stderr (even from F2PY extension modules)
Powerful tools like F2PY and JCC make interfacing with legacy code a breeze. By generating clean Python wrappers around native-language structures, they allow you to seamlessly script and extend them in a much friendlier context:
>>> from legacymodule import SomeFortranAlgorithm as alg
>>> print "Result of running alg on odd numbers 1 to 99:"
>>> for i in xrange(1, 100, 2):
... print "alg(%d)=%s" % (i, alg(i))
...
alg(1)=2.71828182846
alg(3)=20.0855369232
alg(5)=148.413159103
alg(7)=1096.63315843
...
It’s like there’s no Fortran at all! As you can imagine, this has the potential to be extremely useful for a myriad of scientific applications.
However, one problem with bringing legacy code into the future is dealing with its existing interface. If SomeFortranAlgorithm
prints a bunch of über-technical gibberish to stdout with each call, then you could see how that might be a bit of a pain. Unfortunately, the standard Python tricks…
>>> import sys, os
>>> from legacymodule import NoisyAlgorithm as loudmouth
>>> sys.stdout = open(os.devnull, "w")
>>> print "This is going to fall on deaf ears"
>>> answer = loudmouth(36)
COMPUTING RESULT FOR I=36
THE RESULT IS 4.31123154712E+15
>>> # Rarr!
...
…won’t always work. This is because the extension module, native as it is, operates on lower level system APIs. The trick, as revealed in this answer on Stack Overflow, is to overwrite the actual file descriptors (#1 for stdout, #2 for stderr) using os.dup2
:
>>> import sys, os
>>> from legacymodule import NoisyAlgorithm as loudmouth
>>> null_fd = os.open(os.devnull, os.O_RDWR)
>>> ops.dup2(null_fd, 1)
>>> print "This is going to fall on deaf ears"
>>> answer = loudmouth(36)
>>> # Yay! Peace and quiet!
...
After liberal application of syntactic sugar, I came up with a handy, reusable context manager Silence
which will redirect stdout (and stderr, too) with fewer lines than the box office at a Milli Vanilli reunion concert:
>>> with Silence():
... answer = loudmouth(36)
...
>>> # ... crickets ...
...
Made possible by the following context manager. You’ll notice I’ve built in support for redirecting to file (sorry, StringIO
instances don’t have file descriptors). It’s also available as a recipe on ActiveState under the MIT license.
## {{{ http://code.activestate.com/recipes/577564/ (r2)
import os
class Silence:
"""Context manager which uses low-level file descriptors to suppress
output to stdout/stderr, optionally redirecting to the named file(s).
>>> import sys, numpy.f2py
>>> # build a test fortran extension module with F2PY
...
>>> with open('hellofortran.f', 'w') as f:
... f.write('''\
... integer function foo (n)
... integer n
... print *, "Hello from Fortran!"
... print *, "n = ", n
... foo = n
... end
... ''')
...
>>> sys.argv = ['f2py', '-c', '-m', 'hellofortran', 'hellofortran.f']
>>> with Silence():
... # assuming this succeeds, since output is suppressed
... numpy.f2py.main()
...
>>> import hellofortran
>>> foo = hellofortran.foo(1)
Hello from Fortran!
n = 1
>>> print "Before silence"
Before silence
>>> with Silence(stdout='output.txt', mode='w'):
... print "Hello from Python!"
... bar = hellofortran.foo(2)
... with Silence():
... print "This will fall on deaf ears"
... baz = hellofortran.foo(3)
... print "Goodbye from Python!"
...
...
>>> print "After silence"
After silence
>>> # ... do some other stuff ...
...
>>> with Silence(stderr='output.txt', mode='a'):
... # appending to existing file
... print >> sys.stderr, "Hello from stderr"
... print "Stdout redirected to os.devnull"
...
...
>>> # check the redirected output
...
>>> with open('output.txt', 'r') as f:
... print "=== contents of 'output.txt' ==="
... print f.read()
... print "================================"
...
=== contents of 'output.txt' ===
Hello from Python!
Hello from Fortran!
n = 2
Goodbye from Python!
Hello from stderr
================================
>>> foo, bar, baz
(1, 2, 3)
>>>
"""
def __init__(self, stdout=os.devnull, stderr=os.devnull, mode='w'):
self.outfiles = stdout, stderr
self.combine = (stdout == stderr)
self.mode = mode
def __enter__(self):
import sys
self.sys = sys
# save previous stdout/stderr
self.saved_streams = saved_streams = sys.__stdout__, sys.__stderr__
self.fds = fds = [s.fileno() for s in saved_streams]
self.saved_fds = map(os.dup, fds)
# flush any pending output
for s in saved_streams: s.flush()
# open surrogate files
if self.combine:
null_streams = [open(self.outfiles[0], self.mode, 0)] * 2
if self.outfiles[0] != os.devnull:
# disable buffering so output is merged immediately
sys.stdout, sys.stderr = map(os.fdopen, fds, ['w']*2, [0]*2)
else: null_streams = [open(f, self.mode, 0) for f in self.outfiles]
self.null_fds = null_fds = [s.fileno() for s in null_streams]
self.null_streams = null_streams
# overwrite file objects and low-level file descriptors
map(os.dup2, null_fds, fds)
def __exit__(self, *args):
sys = self.sys
# flush any pending output
for s in self.saved_streams: s.flush()
# restore original streams and file descriptors
map(os.dup2, self.saved_fds, self.fds)
sys.stdout, sys.stderr = self.saved_streams
# clean up
for s in self.null_streams: s.close()
return False
## end of http://code.activestate.com/recipes/577564/ }}}