Python 从零开始 · 第 9 课:异常处理与调试——程序不崩的秘密
到目前为止,我们的程序都假设一切顺利。可现实不是:文件可能不存在、用户可能输错、网络可能断、除法的分母可能是 0。程序一遇到这些就崩溃,体验极差。
这一课,我们学会两件事:让程序优雅地处理错误(不崩),以及出错后快速定位问题(调试)。
一、先认识几种常见报错
报错不可怕,看懂报错信息是关键。几种最常见的:
# 1. 名字打错了
print(x) # NameError: name 'x' is not defined
# 2. 类型不对
"5" + 5 # TypeError: can only concatenate str
# 3. 下标越界
[1, 2, 3][5] # IndexError: list index out of range
# 4. 键不存在
{}["a"] # KeyError: 'a',键不存在会直接报错
{}.get("a") # None,用 get 安全查,不报错
# 5. 分母为 0
1 / 0 # ZeroDivisionError
# 6. 文件不存在
open("不存在.txt") # FileNotFoundError
# 7. 类型转换失败
int("abc") # ValueError看报错时,重点看最后一行:错误类型 + 原因,以及上面几行:哪一行代码出的错。
二、try / except:把错误"接住"
用 try 包住可能出错的代码,用 except 接住错误,程序就不会崩:
try:
num = int(input("输入一个数字:"))
print("你输入的是", num)
except ValueError:
print("输入的不是数字!")用户输入了"abc"也不会崩,而是打印提示。
可以接住多种错误,分别处理:
try:
x = int(input("数字:"))
result = 100 / x
print(result)
except ValueError:
print("请输入数字")
except ZeroDivisionError:
print("不能除以 0")也可以不指定类型,兜底接住所有错误(但不推荐滥用,会掩盖真 bug):
except Exception as e:
print("出错了:", e)🧊 冷笑话:"程序员为什么总那么乐观?""因为他只写try,从来不写except。"
三、else / finally:成功时、无论如何时
try:
f = open("data.txt", "r", encoding="utf-8")
except FileNotFoundError:
print("文件不存在")
else:
print("文件打开成功") # try 没出错才执行
finally:
print("无论成功失败都执行") # 总是执行(常用于清理资源)不过读文件我们已经用 with 了,它内部就自带清理逻辑,更推荐。
四、主动抛错:raise
有时候我们要主动"喊停",用 raise:
def divide(a, b):
if b == 0:
raise ValueError("分母不能为 0")
return a / b
divide(10, 0) # 主动抛出 ValueError,并给出明确原因五、调试:定位 bug 的三板斧
板斧一:print 大法
最简单也最常用——在关键处打印变量,看值对不对:
def 总分(scores):
print("进入总分函数,收到:", scores) # 调试打印
return sum(scores)板斧二:Python 自带的 pdb
在代码里插一行 breakpoint()(Python 3.7+),运行到这会暂停,进入交互调试:
def buggy(nums):
total = 0
for n in nums:
total += n
breakpoint() # 停在这,可以敲 n 看值、c 继续
return total板斧三:读报错 + 二分定位
程序很长不知道哪错?从中间开始注释掉一半,看还错不错,反复二分,很快定位到出错的那一行。
六、logging:print 的正式升级版
print 调试法好上手,但真写项目时它有两个硬伤:
- 没级别:调试信息、普通信息、警告、错误全混在一起,上线后还得手动一个个删。
- 没法关:想"平时别打印,出问题再打印",print 做不到。
logging 就是为这个设计的——Python 自带的标准库,按级别记录信息,想开就开、想关就关。
基本用法
import logging
logging.basicConfig(level=logging.INFO) # 设置最低显示级别
logging.debug("调试信息:x=5") # 默认不显示
logging.info("用户登录成功") # 显示
logging.warning("磁盘空间不足") # 显示
logging.error("数据库连接失败") # 显示
logging.critical("系统即将崩溃") # 显示级别从低到高
| 级别 | 用途 | 什么时候用 |
|---|---|---|
DEBUG | 调试细节 | 定位问题时临时开 |
INFO | 正常流程 | 记录"程序做了啥" |
WARNING | 潜在问题 | 不影响运行,但值得注意 |
ERROR | 出错 | 某一步失败了 |
CRITICAL | 致命 | 整个程序要挂了 |
basicConfig(level=...) 设的是"门槛"——低于这个级别的日志不会打印。比如设成 INFO,那 debug 就自动闭嘴,不用你手动删。
写到文件 + 带时间戳
默认日志打到屏幕,想落地到文件(这样程序跑在服务器上也能事后查):
import logging
logging.basicConfig(
filename="app.log", # 写到文件
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s", # 带时间和级别
datefmt="%Y-%m-%d %H:%M:%S",
)
logging.info("启动程序")
logging.error("读取配置失败")跑完打开 app.log,每行都有时间、级别、消息:
2026-09-08 04:40:39 [INFO] 启动程序
2026-09-08 04:40:39 [ERROR] 读取配置失败经验谈:开发时用DEBUG,上线时切成INFO或WARNING。这样调试信息平时自动隐藏,出问题再临时调低级别查,不用改代码。从此告别"上线前满屏找 print 挨个删"。
七、一个真实场景:健壮地读文件
把前面学的组合起来,写一个"怎么折腾都不崩"的读文件函数:
import logging
logging.basicConfig(level=logging.INFO)
def read_file_safely(path):
try:
with open(path, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
logging.warning(f"文件 {path} 不存在,返回空字符串")
return ""
except PermissionError:
logging.error("没有权限读这个文件")
return ""
except Exception as e:
logging.error(f"未知错误: {e}")
return ""
content = read_file_safely("不存在的文件.txt")
print(content)八、动手时间 🎯
实验 1:安全计算器
while True:
expr = input("输入算式(如 10/2,q 退出):")
if expr == "q":
break
try:
a, op, b = expr.split()
a, b = float(a), float(b)
if op == "+":
print(a + b)
elif op == "/":
print(a / b)
else:
print("只支持 + 和 /")
except ZeroDivisionError:
print("不能除以 0")
except ValueError:
print("格式不对,请输入如:10 / 2")实验 2:读取配置文件,缺失则用默认值
import json
def load_config(path):
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
print("配置文件不存在,用默认值")
return {"host": "localhost", "port": 80}
cfg = load_config("config.json")
print(cfg)实验 3:用 breakpoint 调试一段代码
def process(nums):
result = []
for n in nums:
if n > 0:
result.append(n * 2)
return result
breakpoint() # 运行到这里暂停,可敲 process([-1,2,3]) 试
print(process([-1, 2, 3, -5]))九、小结
try/except接住错误,程序不崩;else成功时跑、finally总跑、raise主动抛。- 看报错看最后一行(类型+原因)和上面的行号,别被一堆红字吓住。
- 调试三板斧:print 大法 → breakpoint → 二分注释定位;正式项目用
logging分级记录,DEBUG开发、INFO/WARNING上线,能写文件、带时间戳。
下一课,我们学 面向对象——用 class 把数据和操作打包成"对象",让代码更像真实世界,这是进阶编程的必经之路。
先把「安全计算器」和「健壮读文件」敲熟,异常处理的手感就长在你脑子里了。