I have a Python3 script and I want to optionally redirect stdout
and stderr
to a file. Something like this:
# variable declarations
if log_output:
output_file = open('output.txt', 'w')
sys.stdout = output_file
if log_errors:
errors_file = open('errors.txt', 'w')
sys.stderr = errors_file
# code that uses variables declared above but may exit suddenly
#at the end
if log_output:
output_file.close()
if log_errors:
errors_file.close()
This works, unless my code in the middle decides to quit. Then my files aren't guaranteed to be closed. How can I cleanly close these files no matter what happens in the code and only some of the time? (Normally, I would redirect through the shell, but I'm computing the file names in Python and I don't want to recompute them in various shells. Also, I don't want to put the logic for whether or not to redirect in a shell script. I want those branches in my main code if possible.)
Attempt 1
It seems like context managers would be the way to here, but, when I try to use them, I have to rewrite my code several times and it's not pretty code:
if log_output:
with open('output.txt', 'w') as output_file:
with contextlib.redirect_stdout(output_file):
if log_errors:
with open('errors.txt','w') as errors_file:
with contextlib.redirect_stderr(errors_file):
# log_output and log_errors
# code that uses variables declared above but may exit suddenly
else:
# log_output and not log_errors
# code that uses variables declared above but may exit suddenly
else:
if log_errors:
with open('errors.txt', 'w') as errors_file:
with contextlib.redirect_stderr(errors_file):
# not log_output and log_errors
# code that uses variables declared above but may exit suddenly
else:
# not log_output and not log_errors
# code that uses variables declared above but may exit suddenly
Attempt 2
I decided to make a context manager for it. I think it works, and Python's not yelling at me, but I still can't help but feel it's not too Pythonic and I'm not completely sure it's safe. I'm pushing the if
statements in odd directions. Is there a better way?
@contextlib.contextmanager
def opt_stream(stream, name = None):
if name:
file = open(name,'w')
yield file
file.close()
else:
yield stream
output_name, errors_name = None, None
if log_output:
output_name = 'outputs.txt'
if log_errors:
errors_name = 'errors.txt'
with opt_stream(sys.stdout, output_name) as output_file:
with opt_stream(sys.stderr, errors_name) as errors_file:
with contextlib.redirect_stdout(output_file):
with contextlib.redirect_stderr(errors_file):
# code that uses variables declared above but may exit suddenly