013、print 不止打印:format、sep、end、file 重定向、彩色输出实战
从一次线上事故说起
去年维护一个日志采集服务,凌晨三点被报警电话吵醒——生产环境某个模块的日志文件暴涨到20GB,磁盘直接打满。排查下来,罪魁祸首是一行代码:
print("ERROR: " + str(user_id) + " " + str(error_code) + " " + str(timestamp))
这行代码每秒执行上千次,每次拼接字符串都在创建新对象,更致命的是它默认输出到stdout,而运维同学把stdout重定向到了日志文件。一个简单的print,差点让整个服务宕机。
从那以后,我对print的每个参数都格外敏感。今天就把这些实战经验掰开揉碎讲清楚。
format:别再用加号拼字符串了
上面那段代码,用format改写后性能提升30%以上:
# 别这样写——每次拼接创建3个临时字符串对象
print("ERROR: " + str(user_id) + " " + str(error_code))
# 这样写——格式化字符串只创建一次
print("ERROR: {} {} {}".format(user_id, error_code, timestamp))
# 更推荐f-string(Python 3.6+),性能最好
print(f"ERROR: {user_id} {error_code} {timestamp}")
f-string里还能直接调用函数,调试时特别爽:
# 调试利器:打印变量名和值
print(f"{user_id=}, {error_code=}") # 输出:user_id=12345, error_code=404
这里踩过坑:f-string里的表达式不要太复杂,否则可读性会崩。见过同事写f"{[x*2 for x in data if x > 0]}",调试时自己都看不懂。
sep:分隔符的隐藏用法
sep参数常被忽略,但处理列表输出时特别好用:
# 默认用空格分隔
print("a", "b", "c") # a b c
# 改成逗号
print("a", "b", "c", sep=",") # a,b,c
# 实战:生成CSV行
print("2024-01-15", "ERROR", "timeout", sep=",") # 2024-01-15,ERROR,timeout
有个小技巧:用sep拼接路径时,比os.path.join更直观:
# 别这样写
print("/".join(["home", "user", "data"]))
# 这样写更清晰
print("home", "user", "data", sep="/") # home/user/data
end:控制换行,避免日志错乱
默认print会在末尾加换行符\n,这在写进度条或实时日志时很要命:
# 实时进度条,不换行
for i in range(100):
print(f"\r进度: {i}%", end="", flush=True)
time.sleep(0.1)
flush=True这个参数我经常忘加,结果进度条卡住不动,排查半天才发现是缓冲区没刷新。生产环境写日志时,建议始终加上flush:
# 日志写入,强制刷新缓冲区
print(f"[{timestamp}] {message}", file=log_file, flush=True)
file重定向:把print当日志工具用
这是最实用的技巧。print默认输出到sys.stdout,但可以重定向到任何文件对象:
# 写入文件
with open("app.log", "a") as f:
print(f"[ERROR] {message}", file=f)
# 同时输出到文件和终端
import sys
print(f"[INFO] {message}") # 终端
print(f"[INFO] {message}", file=open("app.log", "a")) # 文件
更骚的操作:重定向到标准错误输出:
import sys
# 错误信息输出到stderr,方便区分
print(f"[FATAL] {message}", file=sys.stderr)
这里踩过坑:重定向到文件后,记得关闭文件句柄。用with语句最安全,否则文件描述符会泄漏。
彩色输出:让日志一目了然
终端彩色输出靠ANSI转义码,原理很简单:
# 颜色代码
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
RESET = "\033[0m"
print(f"{RED}[ERROR]{RESET} 连接超时")
print(f"{GREEN}[OK]{RESET} 请求成功")
封装成函数更实用:
def color_print(text, color="red"):
colors = {
"red": "\033[91m",
"green": "\033[92m",
"yellow": "\033[93m",
"blue": "\033[94m"
}
print(f"{colors.get(color, '')}{text}\033[0m")
color_print("危险操作", "red")
color_print("完成", "green")
注意:Windows终端可能不支持ANSI码,需要先执行os.system("color")启用。macOS和Linux默认支持。
实战:一个完整的日志工具
把上面所有技巧整合起来:
import sys
import datetime
class Logger:
def __init__(self, log_file=None):
self.log_file = log_file
def log(self, level, message):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
formatted = f"[{timestamp}] [{level}] {message}"
# 终端输出带颜色
if level == "ERROR":
print(f"\033[91m{formatted}\033[0m", file=sys.stderr, flush=True)
elif level == "WARN":
print(f"\033[93m{formatted}\033[0m", flush=True)
else:
print(f"\033[92m{formatted}\033[0m", flush=True)
# 文件输出不带颜色
if self.log_file:
with open(self.log_file, "a") as f:
print(formatted, file=f, flush=True)
logger = Logger("app.log")
logger.log("ERROR", "数据库连接失败")
logger.log("INFO", "服务启动成功")
个人经验总结
- 生产环境别用print打日志——用logging模块,但调试阶段print+file重定向比logging灵活得多
- f-string是首选——性能好、可读性强,Python 3.6+项目无脑用
- flush=True是保命符——日志丢失往往是因为缓冲区没刷新
- 彩色输出只在开发环境用——生产环境日志要纯文本,方便grep和日志收集系统
- sep参数能替代join——处理路径、CSV时更简洁
最后说一句:print看似简单,但用好它能让调试效率翻倍。下次遇到诡异bug,试试用print+file重定向把中间变量输出到文件,比断点调试快多了。

3085

被折叠的 条评论
为什么被折叠?



