Search code examples
python-3.xcmdterminalcolorswindows-terminal

CMD ASCII color codes in Python print() statements work only for a while


I use ASCII codes like '\u001b[31m' to color the standard output Python prints to the terminal. Applying this on a large codebase results in a strange problem: it works for a while, then all output just prints in the default color and the ASCII color codes themselves are printed instead. I'll describe my approach and the problem in details below.

Note: I'm using Python 3.11 on Windows 11.

1. A printc() function to print in color

I made this printc() function to put some color (pun intended) into my output:

def printc(*args,
           sep:str=' ',
           end:str='\n',
           file:TextIO=sys.stdout,
           flush:bool=False,
           color:str='default',
           bright:bool=False,
           ) -> None:
    '''
    Print the given text in the requested color. The first four parameters are equal to
    those from the builtin print() function. The last two parameters are special:

        :param color:   Pass a color for the text. Choose one of these:
                            - 'default'
                            - 'black'
                            - 'red'
                            - 'green'
                            - 'yellow'
                            - 'blue'
                            - 'magenta'
                            - 'cyan'
                            - 'white'

        :param bright:  Make the color look brighter.
    '''
    # DEFINE COLORS
    # =============
    color_codes = {
        'default' : '',
        'black'   : '\u001b[30m',
        'red'     : '\u001b[31m',
        'green'   : '\u001b[32m',
        'yellow'  : '\u001b[33m',
        'blue'    : '\u001b[34m',
        'magenta' : '\u001b[35m',
        'cyan'    : '\u001b[36m',
        'white'   : '\u001b[37m',

        'default_bright' : '',
        'black_bright'   : '\u001b[30;1m',
        'red_bright'     : '\u001b[31;1m',
        'green_bright'   : '\u001b[32;1m',
        'yellow_bright'  : '\u001b[33;1m',
        'blue_bright'    : '\u001b[34;1m',
        'magenta_bright' : '\u001b[35;1m',
        'cyan_bright'    : '\u001b[36;1m',
        'white_bright'   : '\u001b[37;1m',

        'reset' : '\u001b[0m',
    }
    assert color in color_codes.keys()

    # PRINT
    # =====
    if bright:
        color = f'{color}_bright'
    print(
        f'{color_codes[color]}{sep.join(args)}{color_codes["reset"]}',
        sep   = sep,
        end   = end,
        file  = file,
        flush = flush,
    )
    return

I then just invoke this function like so:

# Print 'foobar' in bright red color
printc('foobar', color='red', bright=True)

2. Problem explained

I've put this function in a large codebase, and invoke it there in several places. It seems to work for a while - the first few print statements do get printed out in color. Then, at some point, it stops working. The text printed out is no longer colored, but preceded and suffixed by the color codes themselves along with a character:

←[31;1mfoobar←[0m

For example:

enter image description here

As you can see - the coloring works for a while, until it doesn't.

3. A weird 'fix'

I found a very weird 'fix' for this problem. I noticed that putting os.system('') in front of each print statement solves the problem. So my printc() function would then end in:

    ...
    os.system('') # This line somehow fixes the problem
    print(
        f'{color_codes[color]}{sep.join(args)}{color_codes["reset"]}',
        sep   = sep,
        end   = end,
        file  = file,
        flush = flush,
    )
    return

However, I'm not happy to apply this solution. After all, os.system('') attempts to run a shell command. I think it will harm the performance of the software to invoke it before each and every print statement.

Do you know a better fix?


Solution

  • I use the library colorama now. The printc() function I had can stay as-is. I just need to run colorama.init() once.

    Colorama then modifies the behavior of sys.stdout slightly such that the terminal can deal with color sequences like '\u001b[31m'.

    Thank you @Mofi for the help!