Search code examples
pythonloggingconsolespyder

Printing the console to a log file is not reversible in Spyder for Python


I am using Spyder for Python and sometime I would like to print the console into a log file (in cases where the output is quite long) and sometimes I just want to have the output at the console. For this purpose I use the following construction in my Python files:

In the beginning of the file:

import sys
# specify if the output should be printed on a separate log file or on the console
printLogToFile = False

if printLogToFile == True:
    #Specify output file for the logs
   sys.stdout = open('C:/Users/User 1/logfile.txt', 'w')

At the end of the file:

# Close the log file if output is printed on a log file and not on the console
if printLogToFile == True:
    sys.stdout.close()
    sys.stdout = sys.__stdout__

Basically, whenever my boolean variable printLogToFile has the value False then everything is printed on the console as it should and whenever it has the value True everything is printed into the logfile. However, once I run just once the file with printLogToFile=True this can't be reversed any longer. Even when the variable has the value False it still prints everything into the log file and not onto the console. What is even more strange is that also for other Python files, that do not have any connection to this file, the console is not printed any longer onto the console. The only way to solve this problem is to close Spyder and restart it again.

Do you have any idea why this is happening and how to avoid this? I'd appreciate every comment.


Solution

  • The console in Spyder is an IPython console, not a plain Python console, so I think IPython is doing something with stdout that causes your approach to fail.

    The docs for sys.__stdout__ say

    It can also be used to restore the actual files to known working file objects in case they have been overwritten with a broken object. However, the preferred way to do this is to explicitly save the previous stream before replacing it, and restore the saved object.

    In other words, try:

    if printLogToFile:
        prev_stdout = sys.stdout
        sys.stdout = open('C:/Users/User 1/logfile.txt', 'w')
    
    # code that generates the output goes here
    
    if printLogToFile:
        sys.stdout.close()
        sys.stdout = prev_stdout
    

    As an alternative, based on this answer and this answer assuming Python >= 3.7, you can use contextlib and a with statement to selectively capture the output of some of your code. This seems to work for me in Spyder 4 and 5:

    from contextlib import redirect_stdout, nullcontext
    
    if printLogToFile:
        f = open('myfile.txt', 'w')
        cm = redirect_stdout(f)
    else:
        cm = nullcontext()
    
    with cm:
        # code that generates the output goes here
    

    If you want to execute the whole of your Python script myscript.py and capture everything it outputs, it's probably easier to leave your script unmodified and call it from a wrapper script:

    # put this in the same folder as myscript.py
    
    from contextlib import redirect_stdout
    
    with redirect_stdout(open('myfile.txt', 'w')):
        import myscript
     
    

    If you want anything more flexible than that, it's probably time to start using logging.