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)
alvas
  • 115,346
  • 109
  • 446
  • 738
  • why can't you edit the function. This is python and every function is in plain text files. – Daniel Apr 24 '14 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. – Jacobo de Vera Apr 24 '14 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 =) – alvas Apr 24 '14 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)` – Ashwini Chaudhary Apr 24 '14 at 15:04
  • @alvas And use append mode `'a'` to prevent re-writing of data. – Ashwini Chaudhary Apr 24 '14 at 15:06

2 Answers2

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.

Jacobo de Vera
  • 1,863
  • 1
  • 16
  • 20
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)
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • 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. – Jacobo de Vera Apr 24 '14 at 13:55
  • 1
    @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)`. – Ashwini Chaudhary Apr 24 '14 at 13:58
  • @Aशwini चhaudhary, i'm still having problems with the decorator solution, see updated question. – alvas Apr 24 '14 at 14:49