Reputation: 53
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
Upvotes: 0
Views: 370
Reputation: 155516
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.
Upvotes: 2
Reputation: 1561
with FileWrite(..........., sys.stdout) as sys.stdout:
You are overwriting sys.stdout
, just chose a real name for your file like output
or whatever but sys.stdout
.
Example:
with FileWrite(..........., sys.stdout) as output:
output.write('A')
output.write('B')
sys.stdout.write("test")
EDIT
Since you don't really want to write on the standard output, pass your FileWrite
instance as parameter to your methods.
def func1(output):
output.write('A')
with FileWrite(..........., sys.stdout) as output:
func1(output)
Do the same for func2.
Upvotes: 1