tags:

views:

154

answers:

4

Is there a way in python 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... I would appreciate any insight I can get into this.

+7  A: 

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 cStringIO
save_stdout = sys.stdout
sys.stdout = cStringIO.StringIO()
foo()
sys.stdout = save_stdout

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

import contextlib
import sys
import cStringIO

@contextlib.contextmanager
def nostdout():
    save_stdout = sys.stdout
    sys.stdout = cStringIO.StringIO()
    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;-).

Alex Martelli
Good call, sorry, I make lots of mistakes, I am a test as I go guy, very seldom get it right on first try
Tim McJilton
Alex Martelli: that last one seems too pretty. I never have used the "with" keyword before. I am going to have to look into it.
Tim McJilton
It should be noted that if you use this in a threaded environment, your substitution will apply to *all* threads. So if you use it in a threaded webserver for instance, stdout will get trashed for all threads (for the duration of that function call, which will be some random chunk of time in other threads). Handling this case is hard, and would involve `threading.local` (mostly I'd recommend try avoiding threads and stdout redirection).
Ian Bicking
Google Code Jam last week, and posting on stack overflow is humbling, I didn't even think about that. Very valid point.
Tim McJilton
A: 

Rewrite the function so it doesn't write to stdout would be my first impulse. Functions should not arbitrarily print things when they're running, that's quite obnoxious.

If it is displaying output for debugging purposes change it to use a proper logging package that can be more easily enabled and disabled.

John Kugelman
John, that is all good and well if I wrote the function. But when working with many people, and using their API's, we are stuck with their outputs.
Tim McJilton
+1  A: 

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)
try:
    foo()
finally:
    sys.stdout.close()
    sys.stdout = old_stdout
Philipp
The only reason I think it is inefficient because it is a file write. File writes are definitely slower than needed if you are doing it enough times. But yeah I messed up my initial code, I fixed it based of Alex's initial comment
Tim McJilton
It should be `open(os.devnull, 'w')`. In general `os.devnull` is useful if you need *real* file object. But `print` accepts anything with a `write()` method.
J.F. Sebastian
+1  A: 

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)
tgray
tgray: I really like that approach. Is it truly faster? It can save a little bit of time via not doing I guess two jmps and less pushing and pulling from the stack. That and I can over-call all the functions I will call. Is their another hidden advantage that I am not seeing?
Tim McJilton
Tim: It's main benefit is saving programmer time by reducing the number of changes you need to make to your code to turn off stdout for a function. I haven't run a performance test against the contextmanager yet.
tgray
tgray: Thats what I thought. It is a great Idea though. I do like it. I think both approaches are good depending on what you are attempting to do. IE if you are trying to replace a call in 100s of spot that way is better, but if you want to do it for a function being passed into another function, I think the other has its advantages. I do like it though
Tim McJilton
tgray: Random note. After timing the two ways wrapping a function that just does 1+1, because i only care to look at the overhead. That being said it was pretty much a wash. So which ever between the two styles is easiest to implement is the one to go for.
Tim McJilton