Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Silence the stdout of a function in Python without trashing sys.stdout and restoring each function call

Tags:

python

stdout

Is there a way in Python to silence stdout without wrapping a function call like following?

Original Broken Code:

from sys import stdout
from copy import copy
save_stdout = copy(stdout)
stdout = open('trash','w')
foo()
stdout = save_stdout

Edit: Corrected code from Alex Martelli

import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout

That way works but appears to be terribly inefficient. There has to be a better way. Any ideas?

like image 715
Tim McJilton Avatar asked May 13 '10 17:05

Tim McJilton


People also ask

How do you disable stdout in Python?

suppress stdout and stderr with context managerUse suppress_stdout and suppress_stderr flags to indicate which stream to be suppressed. Save the state of the sys. stdout and sys. stderr in the __enter__ function, and redirect them to devnull based on the suppress_stdout and suppress_stderr flags.

What is SYS stdout in Python?

stdout. A built-in file object that is analogous to the interpreter's standard output stream in Python. stdout is used to display output directly to the screen console.

Does print in Python go to stdout?

In Python, whenever we use print() the text is written to Python's sys. stdout, whenever input() is used, it comes from sys. stdin, and whenever exceptions occur it is written to sys. stderr.

What is SYS __ stdout __?

In IDLE, sys. __stdout__ is the program's original standard output - which goes nowhere, since it's not a console application. In other words, IDLE itself has already replaced sys. stdout with something else (its own console window), so you're taking two steps backwards by replacing your own stdout with __stdout__ .


8 Answers

Assigning the stdout variable as you're doing has no effect whatsoever, assuming foo contains print statements -- yet another example of why you should never import stuff from inside a module (as you're doing here), but always a module as a whole (then use qualified names). The copy is irrelevant, by the way. The correct equivalent of your snippet is:

import sys
save_stdout = sys.stdout
sys.stdout = open('trash', 'w')
foo()
sys.stdout = save_stdout

Now, when the code is correct, is the time to make it more elegant or fast. For example, you could use an in-memory file-like object instead of file 'trash':

import sys
import io
save_stdout = sys.stdout
sys.stdout = io.BytesIO()
foo()
sys.stdout = save_stdout

for elegance, a context is best, e.g:

import contextlib
import io
import sys

@contextlib.contextmanager
def nostdout():
    save_stdout = sys.stdout
    sys.stdout = io.BytesIO()
    yield
    sys.stdout = save_stdout

once you have defined this context, for any block in which you don't want a stdout,

with nostdout():
    foo()

More optimization: you just need to replace sys.stdout with an object that has a no-op write method. For example:

import contextlib
import sys

class DummyFile(object):
    def write(self, x): pass

@contextlib.contextmanager
def nostdout():
    save_stdout = sys.stdout
    sys.stdout = DummyFile()
    yield
    sys.stdout = save_stdout

to be used the same way as the previous implementation of nostdout. I don't think it gets any cleaner or faster than this;-).

like image 120
Alex Martelli Avatar answered Oct 05 '22 03:10

Alex Martelli


Just to add to what others already said, Python 3.4 introduced the contextlib.redirect_stdout context manager. It accepts a file(-like) object to which the output is to be redirected.

Redirecting to /dev/null will suppress the output:

In [11]: def f(): print('noise')

In [12]: import os, contextlib

In [13]: with open(os.devnull, 'w') as devnull:
   ....:     with contextlib.redirect_stdout(devnull):
   ....:         f()
   ....:         

In [14]: 

This solution can be adapted to be used as a decorator:

import os, contextlib

def supress_stdout(func):
    def wrapper(*a, **ka):
        with open(os.devnull, 'w') as devnull:
            with contextlib.redirect_stdout(devnull):
                return func(*a, **ka)
    return wrapper

@supress_stdout
def f():
    print('noise')

f() # nothing is printed

Another possible and occasionally useful solution that will work in both Python 2 and 3 is to pass /dev/null as an argument to f and redirect the output using the file argument of the print function:

In [14]: def f(target): print('noise', file=target)

In [15]: with open(os.devnull, 'w') as devnull:
   ....:     f(target=devnull)
   ....:     

In [16]: 

You can even make target completely optional:

def f(target=sys.stdout):
    # Here goes the function definition

Note, you'll need to

from __future__ import print_function

in Python 2.

like image 41
vaultah Avatar answered Oct 05 '22 03:10

vaultah


Chiming in very late to this with what I thought was a cleaner solution to this problem.

import sys, traceback

class Suppressor(object):

    def __enter__(self):
        self.stdout = sys.stdout
        sys.stdout = self

    def __exit__(self, type, value, traceback):
        sys.stdout = self.stdout
        if type is not None:
            # Do normal exception handling

    def write(self, x): pass

Usage:

with Suppressor():
    DoMyFunction(*args,**kwargs)
like image 23
bitmous Avatar answered Oct 05 '22 03:10

bitmous


Why do you think this is inefficient? Did you test it? By the way, it does not work at all because you are using the from ... import statement. Replacing sys.stdout is fine, but don't make a copy and don't use a temporary file. Open the null device instead:

import sys
import os

def foo():
    print "abc"

old_stdout = sys.stdout
sys.stdout = open(os.devnull, "w")
try:
    foo()
finally:
    sys.stdout.close()
    sys.stdout = old_stdout
like image 24
Philipp Avatar answered Oct 05 '22 02:10

Philipp


redirect_stdout() has been added to contextlib since python 3.4

For python >= 3.4, this should do it:

import contextlib
import io

with contextlib.redirect_stdout(io.StringIO()):
    foo()
like image 33
Pin-Yen Avatar answered Oct 05 '22 04:10

Pin-Yen


A slight modification to Alex Martelli's answer...

This addresses the case where you always want to suppress stdout for a function instead of individual calls to the function.

If foo() was called many times would it might be better/easier to wrap the function (decorate it). This way you change the definition of foo once instead of encasing every use of the function in a with-statement.

import sys
from somemodule import foo

class DummyFile(object):
    def write(self, x): pass

def nostdout(func):
    def wrapper(*args, **kwargs):        
        save_stdout = sys.stdout
        sys.stdout = DummyFile()
        func(*args, **kwargs)
        sys.stdout = save_stdout
    return wrapper

foo = nostdout(foo)
like image 24
tgray Avatar answered Oct 05 '22 02:10

tgray


I don't think it gets any cleaner or faster than this;-)

Bah! I think I can do a little better :-D

import contextlib, cStringIO, sys

@contextlib.contextmanager
def nostdout():

    '''Prevent print to stdout, but if there was an error then catch it and
    print the output before raising the error.'''

    saved_stdout = sys.stdout
    sys.stdout = cStringIO.StringIO()
    try:
        yield
    except Exception:
        saved_output = sys.stdout
        sys.stdout = saved_stdout
        print saved_output.getvalue()
        raise
    sys.stdout = saved_stdout

Which gets to what I wanted originally, to suppress output normally but to show the suppressed output if an error was thrown.

like image 23
PatB Avatar answered Oct 05 '22 04:10

PatB


By generalizing even more, you can get a nice decorator that can capture the ouput and even return it:

import sys
import cStringIO
from functools import wraps

def mute(returns_output=False):
    """
        Decorate a function that prints to stdout, intercepting the output.
        If "returns_output" is True, the function will return a generator
        yielding the printed lines instead of the return values.

        The decorator litterally hijack sys.stdout during each function
        execution for ALL THE THREADS, so be careful with what you apply it to
        and in which context.

        >>> def numbers():
            print "42"
            print "1984"
        ...
        >>> numbers()
        42
        1984
        >>> mute()(numbers)()
        >>> list(mute(True)(numbers)())
        ['42', '1984']

    """

    def decorator(func):

        @wraps(func)
        def wrapper(*args, **kwargs):

            saved_stdout = sys.stdout
            sys.stdout = cStringIO.StringIO()

            try:
                out = func(*args, **kwargs)
                if returns_output:
                    out = sys.stdout.getvalue().strip().split()
            finally:
                sys.stdout = saved_stdout

            return out

        return wrapper

    return decorator
like image 21
e-satis Avatar answered Oct 05 '22 02:10

e-satis