在Python开发过程中,观察程序运行状态最直接的方式就是把中间结果输出出来。很多初学者习惯用print把变量值丢到控制台,这在几行脚本里确实省事。但当代码规模扩大到几十个模块、涉及异步任务或后台服务时,杂乱无章的打印内容会让定位问题变得异常困难。我们需要一套能区分信息等级、标明来源并灵活控制的机制,这就是调试信息打印要解决的问题。

为什么不能只依赖print函数
print是Python内置的最简单输出工具,调用print(obj)会把对象转换成字符串写到标准输出。它的优势是零配置、即写即看,适合在写算法题或验证某个函数返回值是否正确时使用。例如下面这段代码,我们想知道循环里每次累加的结果:
def calc_sum(n):
total = 0
for i in range(1, n + 1):
total += i
print("当前i:", i, "total:", total)
return total
calc_sum(5)
上述写法在小脚本里没问题,但缺陷也很明显。首先,print输出没有级别概念,所有内容混在一起,当程序正常上线后你很难一键关闭这些语句。其次,print无法记录发生时间、代码文件和行号,多人协作时根本不知道某行输出来自哪个模块。最后,print默认走sys.stdout,在打包成服务或用uwsgi等托管时,输出可能被丢弃或错乱。因此,哪怕是临时调试,也建议用条件开关控制,例如DEBUG = True然后if DEBUG: print(...),避免遗忘清理。
另一个常被忽视的问题是性能。在高频调用的热点函数中频繁print会引发大量IO等待,拖慢整体吞吐。相比之下,专业的日志框架可以设置为异步或按级别过滤,DEBUG信息在 production 环境直接不处理。所以print只应是“随手验证”的临时手段,不应成为项目长期的调试信息方案。
使用logging模块输出结构化调试信息
Python标准库中的logging模块是打印调试信息的正统选择。它通过Logger、Handler、Formatter三层结构分离“产生信息”“送往哪里”“长什么样”。最快速的入门方式是调用logging.basicConfig配置根日志器,然后直接用logging.debug、logging.info等方法输出。以下示例展示了如何打开DEBUG级别并把时间、级别、消息打印出来:
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logging.debug("数据库连接池初始化完成,大小=%d", 10)
logging.info("用户请求进入,uid=%s", "u_1001")
在这段代码里,level=logging.DEBUG表示DEBUG及以上级别(DEBUG、INFO、WARNING、ERROR、CRITICAL)都会输出。format字符串里的%(asctime)s自动填时间,%(levelname)s填级别名,%(name)s填日志器名称,这样每条调试信息都自带上下文。如果某天不想看DEBUG,只需把level改成logging.INFO,所有debug调用自动沉默,不需要改业务代码。
对于更大项目,应该创建专属Logger而非直接用根日志器,避免第三方库日志干扰。可以通过logger = logging.getLogger("myapp")获取,并添加FileHandler把调试信息写进文件,同时用StreamHandler留一份到控制台。Formatter还能定制到显示%(filename)s:%(lineno)d,精准定位打印位置。这种结构化的方式,让你在排查生产问题时,直接翻日志文件就能还原现场,而不用在代码里翻找print。
结合pdb与日志提升调试效率
除了被动打印,主动断点也是打印调试信息的利器。Python内置的pdb模块允许在代码中插入import pdb; pdb.set_trace(),程序运行到此处会暂停,进入交互式环境。此时你可以输入命令查看变量、调用栈,也能用p variable打印任意表达式,相当于动态的print。下面例子演示在解析配置出错时切入调试:
import pdb
def parse_config(raw):
pdb.set_trace()
name = raw["name"]
return name
cfg = {"age": 18}
parse_config(cfg)
运行后程序停在set_trace行,你能在终端执行p raw看到传入字典没有name键,从而明白后续会报KeyError。这种方式比提前写十几个print更高效,因为你可以按需要临时输出任何状态,而不是事先猜哪里出错。配合logging,还可以在捕获异常时用logger.exception自动记录堆栈,既保留现场又不阻塞流程。
实际工程中,推荐把logging作为常态调试信息通道,把pdb作为线下精准排查工具。例如在Web接口里用logging记录每次请求参数和耗时,当线上反馈异常时,先查日志定位大致模块,再本地用pdb复现断点细看。两者互补,既能应对规模化系统的可观测性需求,也不丢失交互式调试的灵活。切忌在提交代码时遗留pdb.set_trace,否则会让服务卡住,可用pre-commit钩子扫描防止此类问题。