Windows下Python print() ASCII颜色码运行一段时间后失效解决方案
问题根因
Windows 控制台对ANSI转义颜色码的解析能力,依赖输出句柄上的ENABLE_VIRTUAL_TERMINAL_PROCESSING(取值0x0004)标志位:
- 程序启动初期Python默认给控制台句柄开启了该标志,因此彩色输出可以正常工作
- 代码中调用的Win32原生API、第三方控制台操作类库、异常输出流操作都可能意外清除该标志位。标志被清除后转义序列不会被终端解析,ESC控制符会以
←明文形式打印,颜色效果完全失效 os.system('')之所以能临时修复,是因为拉起的cmd子进程会重新为关联控制台设置该标志,但每次调用都需要创建独立进程,高频打印场景下性能损耗极高,不适合生产环境使用。
最优修复方案(零额外依赖、一次配置永久生效)
直接在程序入口处(所有打印逻辑、第三方库初始化逻辑执行前),通过ctypes调用Windows Kernel32 API手动给stdout、stderr句柄永久开启VT处理标志,全程只需要执行1次,无任何运行时额外开销:
import ctypes import sys # Windows Kernel32 控制台相关常量 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 STD_OUTPUT_HANDLE = -11 STD_ERROR_HANDLE = -12 def init_windows_console_vt(): # 非Windows系统、输出流不是交互式终端的场景直接跳过,避免转义码写入日志文件 if sys.platform != "win32": return if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty(): return kernel32 = ctypes.windll.kernel32 # 分别给标准输出、标准错误流设置VT处理标志 for handle_id in (STD_OUTPUT_HANDLE, STD_ERROR_HANDLE): handle = kernel32.GetStdHandle(handle_id) mode = ctypes.c_ulong() if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)): continue # 追加VT处理标志,不修改原有控制台配置 new_mode = mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING kernel32.SetConsoleMode(handle, new_mode) # 程序启动第一时间调用即可 init_windows_console_vt()
如果代码中存在会主动修改控制台模式的第三方库,只需要在这类库的初始化调用完成后再执行一次init_windows_console_vt()即可,不需要在每次打印前重复调用。
可选优化:兼容非TTY输出场景
可以给自定义的printc函数增加TTY判断逻辑,当输出目标不是交互式终端(比如重定向到文件、管道)时自动跳过转义码拼接,避免日志文件中出现无意义的控制字符,同时完全兼容内置print()的参数逻辑:
# 颜色映射表 COLOR_MAP = { "default": "0", "black": "30", "red": "31", "green": "32", "yellow": "33", "blue": "34", "magenta": "35", "cyan": "36", "white": "37" } def printc(*values, sep=" ", end="\n", file=None, flush=False, color="default", bright=False): file = file if file is not None else sys.stdout # 非终端场景直接输出原始文本,不拼接转义码 if not (hasattr(file, "isatty") and file.isatty()): print(*values, sep=sep, end=end, file=file, flush=flush) return # 拼接颜色控制码 color_code = COLOR_MAP.get(color, "0") prefix = f"\u001b[{1 if bright else 0};{color_code}m" suffix = "\u001b[0m" print(prefix + sep.join(map(str, values)) + suffix, sep="", end=end, file=file, flush=flush)
其他可选方案
如果不想直接调用ctypes,可以使用colorama库,它的初始化逻辑本质和上述ctypes实现一致,会自动维护控制台VT标志位,只需要在程序启动时执行一次初始化调用即可,性能远高于反复调用os.system('')。
注意:不要在每次打印时重复执行控制台初始化逻辑,无论是ctypes实现还是第三方库初始化,一次执行即可永久生效,重复调用无意义还会带来不必要的开销。
内容的提问来源于stack exchange,提问作者K.Mulier
相关产品推荐
相关产品推荐

