0

The following lines change the stdout from sys.__stdout__ to a file. This works fine.

import sys
sys.stdout = open("stdout.txt", "a")
print("test")

However, I want the output to the file as well as to sys.__stdout__. What do I need to add so "test" will be written to both?

finefoot
  • 9,914
  • 7
  • 59
  • 102
  • If you're on a unix system, pipe stdout to __tee__ and it will take care of it for you. – John Gordon Oct 22 '17 at 01:08
  • Be wary of replacing standard output like this. The whole point is that it is a file handle specified by the *user* of your program; you are just writing to the file requested by the user. If you want to write to a different file, be explicit: use the `file` argument to `print` to direct output to a specific file. – chepner Oct 22 '17 at 01:11
  • @emmi474 yes, outside of python, on the command line. – John Gordon Oct 22 '17 at 02:11

3 Answers3

2

You can make your own file-like object whose write method sends the data to both places and install it as sys.stdout.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
1

You could do something like this to keep it simple and avoid modifying sys.stdout:

import sys

my_log = open('log.txt', 'a')

def write_out(data):
    print(data, file=sys.stdout)
    my_log.write(data + '\n')

def write_err(data):
    print(data, file=sys.stderr)
    my_log.write(data + '\n')

def main():
    write_out('test out')
    write_err('test err')
  • 1
    That only works if you control all the output code. Of course, if this was an XY issue, that might be just right. – Davis Herring Oct 22 '17 at 01:19
  • @emmi474 You could have variations of `write_out` and `write_err`. –  Oct 22 '17 at 01:23
  • 1
    That's true. If you want _all_ stdout and _all_ stderr to go to your log file no matter what, you'll have to do something like what @DavisHerring suggested. –  Oct 22 '17 at 01:30
  • 1
    @Wyatt: The [XY Problem](http://xyproblem.info/): the OP might really have wanted to duplicate just their own functions’ output. (They since said otherwise.) – Davis Herring Oct 22 '17 at 01:33
1

It's possible to "intercept" calls to sys.stdout.write with a mock_write function which writes the text to a file and calls the original real_write function, afterwards.

import sys

def mock_write(text):
    with open("test.txt", "a") as fh:
        fh.write(text)
    real_write = type(sys.stdout).write
    real_write(sys.stdout, text)

sys.stdout.write = mock_write
print("TEST")

It's rather unlikely, but this solution might stop working with future versions of Python in case the original write method of sys.stdout is changed and becomes incompatible with our mock function.

Also, some functions, like subprocess.Popen for example, don't use sys.stdout.write but directly write to the fileno file descriptor of a file object. So the mock write won't catch that output.

By the way, this approach of replacing existing methods of an object with another function has its limits: For instance, it won't work with magic methods and __slots__. See Adding a Method to an Existing Object Instance for example, for further reading.

finefoot
  • 9,914
  • 7
  • 59
  • 102
  • About my edit: since you don’t need `self` in `mock_write` (because it’s hidden in `real_write`), we can make `sys.stdout.write` be a normal function-valued attribute rather than a method. You could also avoid `real_write` by calling `type(sys.stdout).write(sys.stdout,text)`. – Davis Herring Jul 27 '19 at 17:31
  • They *become* normal functions on lookup, just like normal functions (and `classmethod` objects) become method objects. – Davis Herring Jul 27 '19 at 22:38
  • 1
    For reference: it fails with magic methods because of [how they are looked up](https://docs.python.org/3/reference/datamodel.html#special-method-lookup). `__slots__` prevents assignments it doesn’t mention, and a [non-data descriptor](https://docs.python.org/3/reference/datamodel.html#invoking-descriptors) can prevent reading an instance attribute entirely. – Davis Herring Jul 27 '19 at 23:34