系列文章

Python 从入门到精通

第 22 / 36 篇

从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。

  1. 01
    Python 怎么装、怎么用
  2. 02
    变量、数字和字符串
  3. 03
    列表:把一组数据放在一起
  4. 04
    元组、集合和字典
  5. 05
    条件判断:让程序做选择
  6. 06
    循环:重复的事交给程序
  7. 07
    函数:把代码整理成可复用的块
  8. 08
    模块与包:拆分你的程序
  9. 09
    输入、输出与字符串格式化
  10. 10
    文件读写:保存程序的数据
  11. 11
    异常处理:程序出错时怎么办
  12. 12
    基础阶段练习:命令行记账本
  13. 13
    类和对象:面向对象入门
  14. 14
    继承、组合与特殊方法
  15. 15
    迭代器与生成器
  16. 16
    列表推导式与生成器表达式
  17. 17
    装饰器:给函数增加能力
  18. 18
    上下文管理器与 with
  19. 19
    类型标注与 dataclass
  20. 20
    正则表达式:从文本中找规律
  21. 21
    日期、时间与时区
  22. 22
    日志与调试正在阅读
  23. 23
    虚拟环境与依赖管理
  24. 24
    测试:让修改不再提心吊胆
  25. 25
    网络请求:用 Python 调用 API
  26. 26
    网页解析与合规采集
  27. 27
    操作 Excel、CSV 与批量文件
  28. 28
    SQLite:给程序加一个数据库
  29. 29
    数据分析入门:NumPy 与 Pandas
  30. 30
    画图:把数据变得直观
  31. 31
    Flask 入门:做一个小网站
  32. 32
    异步编程:同时处理多项任务
  33. 33
    线程、进程与并发选择
  34. 34
    性能分析与优化
  35. 35
    项目结构、配置与发布
  36. 36
    综合项目:从需求到上线

查看整个系列 →

程序出错时,最怕的不是报错,而是不知道出了什么错。这一篇讲两件事:用 logging 模块规范地记录日志,以及一些实用的调试思路。把这两样练熟,排查问题的速度能快一倍。

print 调试的局限

新手调试全靠 print,但项目一大它就顶不住了:

  • print 输出混在正常输出里,分不清
  • 上线后 print 打给谁看?没人看,还拖慢性能
  • 想关掉调试信息,得手动删一堆 print

logging 模块就是 print 的正式版:能分级、能写文件、能随时开关。

快速上手:basicConfig

import logging

logging.basicConfig(level=logging.INFO)   # 设置最低级别

logging.debug("这是调试信息")       # 默认不显示(级别低于 INFO)
logging.info("程序启动了")
logging.warning("磁盘空间不足 10%")
logging.error("连接数据库失败")
logging.critical("系统无法继续运行")

输出(默认格式:级别:日志器名:消息):

INFO:root:程序启动了
WARNING:root:磁盘空间不足 10%
ERROR:root:连接数据库失败
CRITICAL:root:系统无法继续运行

五个级别从低到高:DEBUG < INFO < WARNING < ERROR < CRITICALlevel= 设了门槛,低于它的不输出。开发时设 DEBUG,上线设 WARNING,一行改全局。

记录到文件 + 带时间

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    filename="app.log",          # 写文件(不写则输出到控制台)
    encoding="utf-8",
)
logging.info("用户登录成功")
logging.error("数据库超时")

app.log 里:

2026-08-14 16:30:00 [INFO] root: 用户登录成功
2026-08-14 16:30:01 [ERROR] root: 数据库超时

格式符:%(asctime)s 时间、%(levelname)s 级别、%(name)s 日志器名、%(message)s 消息。加了 filename 就只写文件不打印控制台,两个都要看下面”双输出”。

用模块名做日志器

大型项目里每个模块建自己的 logger,日志里能看到是哪个模块发的:

# 每个 .py 文件顶部都这样写
import logging
logger = logging.getLogger(__name__)   # __name__ 就是模块名

def process():
    logger.info("开始处理")
    try:
        ...
    except ValueError as e:
        logger.error(f"处理失败:{e}", exc_info=True)   # 带堆栈,排查时才能定位

日志会显示 mainutils.file_ops 这样的来源,多模块项目排查问题能精准定位。

控制台 + 文件双输出

想要”控制台看实时,文件留档案”,配置两个 handler:

import logging

logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)

# 控制台 handler
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# 文件 handler
file_h = logging.FileHandler("app.log", encoding="utf-8")
file_h.setLevel(logging.DEBUG)

fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
console.setFormatter(fmt)
file_h.setFormatter(fmt)

logger.addHandler(console)
logger.addHandler(file_h)

logger.debug("文件里会有,控制台没有(级别不同)")
logger.info("两边都有")

理解即可,真正要配的时候照着抄。

异常要记完整堆栈

记日志时带上异常信息,用 exc_info=True 或直接 logger.exception

import logging

logger = logging.getLogger("app")

try:
    1 / 0
except ZeroDivisionError:
    logger.error("计算出错", exc_info=True)   # 记完整堆栈
    # 或 logger.exception("计算出错")  # 只能在 except 块里用,自动带堆栈

只有错误消息没有堆栈,等于没记——你根本不知道错在哪一行。日志里永远带堆栈。

调试思路:三板斧

除了日志,还有几个实用调试手段:

# 1. 断言:验证"应该成立"的条件
def withdraw(balance, amount):
    assert amount > 0, "取款金额必须为正"
    if amount > balance:
        raise ValueError("余额不足")
    return balance - amount

# 2. 手动断点:pdb(交互式调试)
def buggy(x):
    import pdb
    pdb.set_trace()          # 程序跑到这里暂停,进入调试器
    return x * 2

# 在调试器里可以:n 下一步、p 变量名 打印、q 退出

# 3. 二分定位法
# 在疑似出错的代码段前后各加一行日志,
# 看哪一半还在执行,逐步缩小范围——比瞎猜快得多

调试的通用套路:先复现 → 加日志缩小范围 → 定位到具体行 → 想清楚为什么 → 修复 → 加测试防止再犯

新手坑

坑 1:basicConfig 只生效一次。重复调用不覆盖,第二次的配置被忽略。配置放程序入口,别放模块里。

坑 2:日志打敏感信息。密码、token、手机号别进日志——日志可能被任何人翻看,这是安全事故。

坑 3:except 里不记日志。except: pass 加日志是底线,至少 logger.exception(...)

小结

  • logging 五级别:DEBUG < INFO < WARNING < ERROR < CRITICAL,basicConfig 设门槛
  • 格式:%(asctime)s %(levelname)s %(name)s %(message)s,可写文件
  • 每模块 logger = logging.getLogger(__name__),日志带来源
  • 异常必记堆栈:exc_info=Truelogger.exception
  • 调试三板斧:assert、pdb 断点、日志二分定位

练习

  1. 写一个带日志的简单程序:记录”开始”、”处理成功/失败”,失败时带堆栈
  2. 把之前记账本的 add_record 加上日志:记录每次记账的类别和金额(不记备注,防敏感)
  3. 思考:生产环境日志级别应该设什么?如果设成 DEBUG 会有什么问题(提示:量、性能、敏感信息)?