mortuzahasan mortuzahasan - 2 months ago 7
Python Question

context manager I/O operation in file

I am using a context manager to wrap the text which would show in terminal and write to file at the same time.

I faced this problem and got the solution, please check
Writing terminal output to terminal and to a file?

Cannot change the functions (etc. func1 and func2) problem is after the 'with' statement any output as sys.stdout.write its showing value error: I/O operation in closed file

sample code:

import sys, datetime

class FileWrite(object):
def __init__(self,log_file_name, stdout):

self.log_file_name = log_file_name
self.stdout = stdout

def __enter__(self):
self.log_file = open(self.log_file_name, 'a', 0)
return self

def __exit__(self, exc_type, exc_value, exc_traceback):
self.log_file.close()

def write(self, data):
self.log_file.write(data)
self.stdout.write(data)
self.stdout.flush()
def func1():
sys.stdout.write('A')
def func2():
sys.stdout.write('B')
def main():
with FileWrite(..........., sys.stdout) as sys.stdout:
func1()
func2()



sys.stdout.write('test')

main()
............................
# both output A and B is showing in terminal and writing in file
............................
# writing 'test' only in terminal......
I/O operation in closed file

Answer

You don't really need (or want) to use as here. If the goal is to convert any write to sys.stdout to a write to both sys.stdout and your log file, you need to backup sys.stdout on __enter__ and restore it on __exit__, but don't explicitly pass sys.stdout to the constructor, and don't use the __enter__ return to replace sys.stdout, because that bypasses the __enter__/__exit__ code. Instead, have __enter__ and __exit__ do the replacing work for you:

class tee_stdout(object):

    def __init__(self, log_file_name):
        self.log_file_name = log_file_name
        self.stdout = None

    def __enter__(self):
        self.log_file = open(self.log_file_name, 'a', 0)

        # Replace sys.stdout while backing it up
        self.stdout, sys.stdout = sys.stdout, self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        sys.stdout = self.stdout  # Restore original sys.stdout
        self.log_file.close()

    def write(self, data):
        self.log_file.write(data)
        self.stdout.write(data)
        self.stdout.flush()

Now, usage is just:

 with tee_stdout(logfilename):
     ... do stuff that uses sys.stdout, explicitly or implicitly ...
 ... when block exits, sys.stdout restored, so normal behavior resumes ...

Note: If you're targeting Python 3.4 or higher, I'd recommend implementing the class with just write, and then using contextlib.redirect_stdout to avoid reinventing the wheel:

from contextlib import redirect_stdout

class tee_output:
    def __init__(self, *targets):
        self.targets = targets
    def write(self, data):
        for tgt in self.targets:
            tgt.write(data)
            tgt.flush()

with open(logfilename, 'a') as log, redirect_stdout(tee_output(log, sys.stdout)):
    ... logs to logfilename and sys.stdout when sys.stdout written to ...
... undoes redirection ...

Note: All the above aside, usually, you want to just use the logging module and logger methods for stuff like this. You can pre-configure different loggers, some that go to sys.stdout, some that go to a log file and sys.stdout, some that go just to log files, and use the appropriate one when needed.

Comments