0

I have a function that I cannot edit and it prints results onto the console. Is it possible to calls that function but pipes the console output into a file without changing the internal code of the function?

For example, I have:

def somefunc(x):
  print "Hello World", x**x

def pipeout(func, outfilename)
  with open('outfilename', 'w') as fout:
    # somehow pipes out the output...

I couldn't use logger because I couldn't edit the somefunc().

I have tried @Aशwini चhaudhary 's solution but i couldn't the files were empty other than the first outputfile and also it overwrites that file again and again.:

def redirect_output(file_name):
  def decorator(func):
    def wrapper(*args):
      with open(file_name, 'w') as f:
        original_stdout = sys.stdout
        sys.stdout = f
        func(*args)
      sys.stdout = original_stdout
    return wrapper
  return decorator

def somefunc(x):
  print x*x

xs = [1,2,3,4]

for i in xs:
  outputfilename = 'results/'+str(i)
  somefunc = redirect_output(outputfilename)(somefunc)
  somefunc(i)
5
  • why can't you edit the function. This is python and every function is in plain text files. Commented Apr 24, 2014 at 13:34
  • 1
    Might be a library function and needs to be used in some project that will get deployed. Of course you can edit it, but sometimes you should not. Commented Apr 24, 2014 at 13:45
  • yep, logically i can edit the function but it will cause all sorts of complication between me and the other developers and it's best to avoid merge conflict and fight over scrum meeting =) Commented Apr 24, 2014 at 13:50
  • @alvas Don't assign somefunc to a new function again and again inside the loop, either use a different variable name or directly call redirect_output(outputfilename)(somefunc)(i) Commented Apr 24, 2014 at 15:04
  • @alvas And use append mode 'a' to prevent re-writing of data. Commented Apr 24, 2014 at 15:06

2 Answers 2

2

Yes, you can set the value of sys.stdout to another open file with write access. Then you can set it back with sys.__stdout__

I had this need a while ago and made a context manager:

import sys                                                                                                                          
from StringIO import StringIO 

class OutStreamCapture(object):
    """
    A context manager to replace stdout and stderr with StringIO objects and
    cache all output.
    """

    def __init__(self):
        self._stdout = None
        self._stderr = None
        self.stdout = None
        self.stderr = None

    def __enter__(self):
        self._stdout = sys.stdout
        self._stderr = sys.stderr
        sys.stdout = StringIO()
        sys.stderr = StringIO()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Restore original values of stderr and stdout.
        The captured contents are stored as strings in the stdout and stderr
        members.
        """
        self.stdout = sys.stdout.getvalue()
        self.stderr = sys.stderr.getvalue()
        sys.stdout = self._stdout
        sys.stderr = self._stderr

You can use it like this:

with OutStreamCapture() as osc:
    somefunc(x)

And then is osc.stdout and osc.stderr you have two strings with anything the function put in stdout and stderr, respectively.

This could be modified to use arbitrary files instead.

Please note that here I am caching the current value of sys.stdout in the context manager rather than using sys.__stdout__ to restore. This is because at the point we enter this context, stdout might already be redirected, and we want to put it back to what it was.

Sign up to request clarification or add additional context in comments.

3 Comments

i'm getting AttributeError: __exit__ from the OutStreamCapture class
is it because py2.x doesn't have __exit__ by default?
@alvas sorry, missed parentheses with OutStreamCapture() as osc:. Try it again please.
1

You can use a decorator to redirect the sys.stdout to a file object when the function gets called and later restores it back to original STDOUT.

import sys

def redirect_output(file_name):

    def decorator(func):
        def wrapper(*args):
            with open(file_name, 'w') as f:
                original_stdout = sys.stdout
                sys.stdout = f
                func(*args)
            sys.stdout = original_stdout
        return wrapper
    return decorator

@redirect_output('file.txt')
def somefunc(x):
  print "Hello World", x**x


somefunc(2)

print 'Hello to console.'

Output:

>>> %run so.py
Hello to console.
>>> !cat file.txt
Hello World 4

Update:

Working version of your latest code:

for i in xs:
  outputfilename = 'results/'+str(i)
  new_func = redirect_output(outputfilename)(somefunc)
  #or call it directly
  #redirect_output(outputfilename)(somefunc)(i)
  new_func(i)

3 Comments

The decorator, however, requires an extra level of indirection, since OP can't modify somefunc, I'd assume he also can't decorate it, so he'd need to write a decorated wrapper. And in that case, you might as well drop the whole decorator and just do the simple wrapper.
@JacobodeVera Well if they are importing the function from a module then they can use the other decorator notation: somefunc = redirect_output('file.txt')(somefunc).
@Aशwini चhaudhary, i'm still having problems with the decorator solution, see updated question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.