导读:本期聚焦于小伙伴创作的《Python 如何让 print() 输出到文件的同时保持彩色(ANSI)》,敬请观看详情。把日志同时写进文件和终端时,很多人发现原本好看的红色报错变成了普通文本,彩色消失。问题出在 Python 的 print 默认根据输出对象是否带 tty 属性决定是否走颜色包装。若用普通 open 得到的文件对象,系统认为不是终端,第三方库常自动剥离 ANSI 转义码。其实只要手动写入带转义序列的字符串,并选用支持原始字节流的写入方式,文件里就能原样保留 \033[31m 这类控制符。配合 tee 类封装或 logging 的 StreamHandler 桥接,可让同一份带色内容既落盘又不失真,后续用 cat 或支持 ANSI 的查看器打开依旧鲜艳。

在 Python 调试或写命令行工具时,我们常希望终端里用红色标出错误、绿色标出成功,同时把这些日志存到文件备查。但不少人发现,一旦把 print() 重定向到文件,颜色就没了。这并非 Python 删除了颜色,而是输出目标从“终端”变成“普通文件”后,部分封装库主动忽略了 ANSI 转义序列。只要理解原理并做一点封装,就能让文件原样保留彩色控制符。

Python 如何让 print() 输出到文件的同时保持彩色(ANSI)

为什么 print 到文件会丢失颜色

ANSI 颜色本质是形如 33[31m 的转义序列,它告诉支持该协议的终端如何着色。当你在交互式终端执行 print('33[31m错误33[0m'),屏幕显示红字。但若把输出通过 open('log.txt','w') 写入,文件本身不解释颜色,只会存下这些字符。真正让“颜色消失”的,往往是像 colorama 这类库:它们检测输出流是否是 tty(终端),如果不是就自动把转义符过滤掉。

Python 内置的 sys.stdout 在终端是带 isatty() 返回 True 的对象;而文件对象 isatty() 为 False。很多彩色日志方案基于此做降级。因此,保持彩色的关键就是绕过这种自动降级,强制把原始转义序列写进文件。

基础方案:直接写转义序列到文件

最简单的方法是不依赖任何第三方库,自己拼接 ANSI 码并用 print 的 file 参数指定输出。下面代码同时打印到终端和文件,且文件保留颜色:

import sys

RED = '33[31m'
RESET = '33[0m'

def print_color(msg, file=None):
    line = RED + msg + RESET
    # 终端总输出
    print(line, file=sys.stdout)
    # 如果传了文件对象,也写一份
    if file is not None:
        print(line, file=file)

with open('app.log', 'w') as f:
    print_color('数据库连接失败', file=f)

这段代码里,app.log 中会包含 33[31m数据库连接失败33[0m 原始字符。用 cat 或 VS Code 等支持 ANSI 的查看器打开,依旧是红色。注意用文本模式写即可,不需要二进制模式,因为转义序列就是普通字符。

缺点是手动拼字符串繁琐。若团队已用 colorama 或 rich,需配置它们不剥离文件流的颜色。

使用 colorama 强制保留颜色

colorama 通常在 Windows 下把 ANSI 转成 Win32 调用,在非 tty 时关闭转换。我们可以初始化时传入 strip=False 来禁止剥离:

import colorama
from colorama import Fore, Style

# strip=False 表示不对非 tty 输出剥离 ANSI
colorama.init(strip=False)

with open('color.log', 'w') as f:
    # 同时输出到文件和屏幕
    print(Fore.RED + '权限不足' + Style.RESET_ALL, file=f)
    print(Fore.RED + '权限不足' + Style.RESET_ALL)

这样 color.log 里就有完整 Fore.RED 对应的转义序列。该方式兼容已有 colorama 代码,只需改一处 init。但需注意,若文件被不支持 ANSI 的程序读取,会看到乱码状字符,这是预期现象。

如果希望更优雅,可封装一个多目标 writer,把每次写操作复制给终端和文件,避免重复 print 调用。

封装 Tee 类同时写屏与文件

模仿 Linux tee 命令,写一个类接收多个流,任何写入都广播出去。结合 print 的 file 参数即可:

import sys

class Tee:
    def __init__(self, *streams):
        self.streams = streams
    def write(self, data):
        for s in self.streams:
            s.write(data)
    def flush(self):
        for s in self.streams:
            s.flush()

# 终端和标准文件都保留
tee = Tee(sys.stdout, open('multi.log', 'w'))

print('33[32m任务完成33[0m', file=tee)
tee.flush()

Tee 对象不是 tty,但因为我们没用自动剥离库,转义序列直接落地。它把一份内容同时推给屏幕和文件,逻辑清晰,也方便扩展成网络发送。

要注意文件对象用完应关闭,生产环境可包成上下文管理器。此外 logging 模块也能接 StreamHandler 指向 Tee,统一日志格式。

与 logging 模块结合

若项目用 logging,可添加 StreamHandler 并把 stream 设为上面的 Tee,这样 logger.info 带色也能进文件:

import logging
import sys

class Tee:
    def __init__(self, *streams):
        self.streams = streams
    def write(self, data):
        for s in self.streams:
            s.write(data)
    def flush(self):
        for s in self.streams:
            s.flush()

tee = Tee(sys.stdout, open('run.log', 'w'))
handler = logging.StreamHandler(tee)
logging.basicConfig(level=logging.INFO, handlers=[handler])

# 手动加色
logging.info('33[33m警告:磁盘超过80%33[0m')

这样 run.log 中保留黄色警告原文。logging 本身不处理颜色,只负责传递字符串,因此只要输入端带了 ANSI 且 handler 不过滤,文件就原样存储。

总结来说,让 print 输出到文件且保留彩色,核心就是别让库“好心”剥掉转义符,并用多路写流确保同一份带色文本落盘。根据项目复杂度,从直接写文件到 Tee 封装都是可行路径。

PythonprintANSI_color修改时间:2026-08-10 03:42:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。