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", "服务启动成功")

个人经验总结

  1. 生产环境别用print打日志——用logging模块,但调试阶段print+file重定向比logging灵活得多
  2. f-string是首选——性能好、可读性强,Python 3.6+项目无脑用
  3. flush=True是保命符——日志丢失往往是因为缓冲区没刷新
  4. 彩色输出只在开发环境用——生产环境日志要纯文本,方便grep和日志收集系统
  5. sep参数能替代join——处理路径、CSV时更简洁

最后说一句:print看似简单,但用好它能让调试效率翻倍。下次遇到诡异bug,试试用print+file重定向把中间变量输出到文件,比断点调试快多了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值