很多 Python 程序从第一行 print("Hello") 开始,但 print() 远不止把字符串显示在终端上。它可以控制分隔符、行尾、输出流和刷新行为,也能通过格式化表达式生成更易读的诊断信息。理解这些参数后,你可以写出更清晰的命令行工具,并避开一些常见的输出陷阱。
先看清 print() 的完整接口
print() 的常用签名如下:
print(*objects, sep=' ', end='\n', file=None, flush=False)
*objects:要输出的一个或多个对象。对象会先经过str()转换。sep:多个对象之间的分隔符,默认是一个空格。end:输出结尾,默认是换行符。file:输出目标,默认是标准输出sys.stdout。flush:是否立即刷新输出缓冲区,默认是False。
例如,下面的代码可以生成一行 CSV 风格的结果,并且不在末尾追加换行:
values = ["python", "3.12", "ready"]
print(*values, sep=",", end="\n")
print("正在处理", end="", flush=True)
print(" 完成")
运行结果:
python,3.12,ready
正在处理 完成
这里使用 *values 把列表展开为多个位置参数。直接写 print(values) 则会输出 Python 列表表示形式,例如 ['python', '3.12', 'ready'],两者用途不同。
sep 和 end:控制输出形状
默认情况下,多个参数用空格分隔,输出结束后换行:
print("name", "status")
# name status
需要自定义布局时,可以显式设置 sep 和 end:
print(2025, 3, 8, sep="-")
print("下载中", end="...")
print("完成")
结果是:
2025-3-8
下载中...完成
end="" 常用于进度显示或同一行更新。不过,终端输出通常经过缓冲;如果程序需要让用户立即看到状态,就配合 flush=True:
import time
for step in range(3):
print(f"step {step + 1}/3", end="\r", flush=True)
time.sleep(0.5)
print("step 3/3 done")
\r 会把光标移回当前行开头。需要注意,进度动画适合交互式终端;当输出被重定向到文件或 CI 日志时,频繁使用 \r 可能让日志难以阅读。
格式化比字符串拼接更可靠
不要把多个变量通过大量 + 拼接起来:
name = "Ada"
count = 7
print("user: " + name + ", count: " + str(count))
这段代码能工作,但可读性和扩展性都一般。更推荐 f-string:
name = "Ada"
count = 7
ratio = 0.875
print(f"user: {name}, count: {count}, success: {ratio:.1%}")
输出:
user: Ada, count: 7, success: 87.5%
f-string 还可以处理宽度、对齐和调试表达式:
service = "api"
latency_ms = 12.3456
print(f"{service:<10} | {latency_ms:>8.2f} ms")
print(f"{latency_ms=:.2f}")
如果输出是给机器读取的协议或数据文件,手写分隔符要格外谨慎。包含逗号、换行或引号的字段可能破坏格式;这类场景应使用 csv、json 等专门模块。
file 和 flush:输出不一定去终端
print() 的 file 参数接受具有 write() 方法的对象,因此可以把输出写入文件:
from pathlib import Path
log_path = Path("run.log")
with log_path.open("w", encoding="utf-8") as log_file:
print("job started", file=log_file)
print("job finished", file=log_file)
也可以显式写入标准错误流。错误信息和正常结果分流后,命令行调用者可以分别重定向它们:
import sys
print("正常结果")
print("配置缺失", file=sys.stderr)
在 shell 中可以这样运行:
python app.py > output.txt 2> error.txt
flush=True 会立即调用输出流的刷新操作。它适合长时间运行程序的状态提示、交互式脚本和需要实时观察的子进程通信,但不是解决所有日志问题的办法。频繁刷新会带来额外开销;正式服务通常应使用日志库。
什么时候不该继续使用 print()
print() 很适合:
- 一次性脚本的少量诊断信息;
- 教学示例和交互式探索;
- 简单命令行工具的最终结果;
- 小程序中明确的标准输出或错误输出。
当程序需要以下能力时,logging 往往更合适:
- 日志级别,例如 DEBUG、INFO、WARNING 和 ERROR;
- 时间戳、模块名、请求 ID 等上下文;
- 同时写入控制台和文件;
- 统一的格式和可配置的输出目标。
一个最小的日志配置示例:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
logging.info("worker started")
logging.warning("retrying request")
如果目标是输出结构化数据,使用 JSON 序列化而不是依赖 print() 的默认表示:
import json
result = {"ok": True, "items": 3}
print(json.dumps(result, ensure_ascii=False))
这样下游程序能稳定解析结果,也不会因为 Python 字典表示形式的变化而依赖实现细节。
一份实用检查清单
使用 print() 前,可以快速确认:
- 输出是给人看的,还是给程序解析的?
- 多个值是否需要明确的
sep? - 是否应该保留或去掉末尾换行?
- 输出是否必须立即出现?必要时使用
flush=True。 - 错误信息是否应该写入
sys.stderr? - 代码是否已经需要日志级别、持久化或统一格式?如果需要,尽早切换到
logging。
print() 的价值不在于它简单,而在于它的行为足够明确。掌握 sep、end、file 和 flush 后,它可以承担可靠的小型输出任务;当输出开始承载调试、运维或数据交换职责时,再让专门的日志和序列化工具接手。