欢迎光临
我们一直在努力

013、print 不止打印:format、sep、end、file 重定向、彩色输出实战

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重定向把中间变量输出到文件,比断点调试快多了。

    赞(0)
    未经允许不得转载:171主机测评 » 013、print 不止打印:format、sep、end、file 重定向、彩色输出实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址