在 Python 程序里,print 是最常用的输出方式,但它默认把内容写到 sys.stdout。当我们开发命令行工具、守护进程或者需要在管道中区分正常数据和错误信息时,让 print 默认输出到 stderr 会更合理。本文介绍几种让 print 默认走 stderr 的实现方式,并分析它们的适用场景。

为什么需要把 print 指向 stderr
标准输出 stdout 通常被用作程序正常结果的通道,比如返回处理好的数据;而标准错误 stderr 专门承载警告、错误和调试信息。如果把所有内容都混在 stdout 里,当其他程序通过管道只取 stdout 数据时,就会把日志也一并收走,造成解析失败。
例如一个脚本从文件读取内容并输出转换结果,同时用 print 打印进度。若进度信息进入 stdout,下游命令就会收到脏数据。将这类信息改到 stderr,既能保证 stdout 干净,又能在终端同时看到运行轨迹。
方法一:每次调用 print 时指定 file 参数
最直接的方式是在调用 print 时传入 file=sys.stderr。这种方式不需要改动全局状态,适合少量、明确的输出位置调整。
下面代码演示了基本用法:
import sys
def main():
print("这条信息去 stderr", file=sys.stderr)
print("这条信息去 stdout")
if __name__ == "__main__":
main()
这种写法的优点是意图清晰、不影响其他代码;缺点是每个 print 都要手写参数,项目变大后容易遗漏,也不利于统一切换输出目标。
方法二:封装一个默认走 stderr 的打印函数
为了避免重复写 file 参数,可以封装一个类似 print 的函数,把 file 固定为 sys.stderr,同时保留原有排版能力。
示例实现如下:
import sys
def eprint(*args, **kwargs):
# 默认输出到 stderr,其余参数与 print 一致
kwargs.setdefault("file", sys.stderr)
print(*args, **kwargs)
eprint("错误:配置文件不存在")
eprint("当前计数", 10, sep="=")
使用 eprint 能统一团队输出规范,而且可以随时改 eprint 内部逻辑,比如加上时间戳。但它要求大家记得调用 eprint 而不是 print,对老代码迁移仍有一定成本。
方法三:在程序启动时替换 sys.stdout
如果希望全局所有 print 都默认去 stderr,可以在入口处把 sys.stdout 指向 sys.stderr。这样既有代码完全不用改。
参考代码:
import sys
# 在程序最前面执行
sys.stdout = sys.stderr
print("现在这条也会出现在 stderr")
这种方案侵入最小,但风险也明显:任何依赖真正 stdout 的库都可能出错,而且子进程、多线程下如果流被重定向,行为会难以预期。一般只建议在独立小工具里使用。
方法四:利用 sitecustomize 做全局改写
Python 启动时会自动导入 sitecustomize 模块。我们可以在其中 monkey patch 内置 print 或 sys.stdout,让所有脚本默认走 stderr。
在 sitecustomize.py 中写入:
import sys
import builtins
# 将内置 print 的默认 file 改为 stderr
original_print = builtins.print
def patched_print(*args, **kwargs):
kwargs.setdefault("file", sys.stderr)
original_print(*args, **kwargs)
builtins.print = patched_print
这种办法适合公司内统一环境,不需要改业务代码。但会影响机器上所有 Python 程序,调试第三方工具时可能带来困惑,因此要谨慎启用,并留下开关。
不同方案对比
我们用一张表总结上述做法的特点:
| 方案 | 作用范围 | 代码改动 | 风险 |
|---|---|---|---|
| 指定 file 参数 | 单条语句 | 每处手写 | 低 |
| 封装 eprint | 调用处 | 中 | 低 |
| 替换 sys.stdout | 整个进程 | 极小 | 中 |
| sitecustomize | 所有脚本 | 无业务改动 | 高 |
从表中可以看出,越全局的方案越方便,但副作用也越大。实际选型时要权衡项目规模和维护边界。
多进程与重定向注意事项
当使用 multiprocessing 或 subprocess 时,子进程会继承父进程的标准流。如果父进程把 sys.stdout 指向了 stderr,子进程不重新设置就可能写错地方。
建议在子进程入口也显式设定,或者干脆用 eprint 这类函数而不是依赖全局替换。另外,若用 shell 把 stdout 重定向到文件,stderr 仍会打印到终端,这正是我们想要的分离效果。
总结建议
对于临时调试,直接传 file=sys.stderr 最省事;对于长期项目,封装 eprint 并逐步替换 print 更稳妥;只有在写一次性小工具且确认无 stdout 依赖时,才考虑替换 sys.stdout。理解这些机制后,你就能按需要让 print 默认输出到 stderr 而不是 stdout。