⌨️ 第四章 流程图与算法
📄 一句话概括

程序跑着跑着会撞上【预料之外】:除零、文件不存在、网络断线——异常是出错时的【警报弹起】,异常处理(try/except)是提前铺好的【安全气垫】:报警了不崩车,接住、处理、继续跑。

📝 本节和前面几节的关系

4-1~4-7 都在【一切正常】的假设下写代码,这一篇补上【不正常时怎么办】。回收两处旧地:数据库课第四章的事务(出错全回滚)是异常处理的业务版;C++ 课第十四章的 try/catch 是同一机制的另一门语法。

一、异常:出错的警报

错误在 Python 里不是【返回个错误码】而是弹起一个异常对象——不接住就一路往上冒,冒到顶程序终止:

print(10 // 0)
# ZeroDivisionError: division by zero

常见异常族(每个都是一种类型,可分别接住):

异常 什么时候弹
ZeroDivisionError 除零
FileNotFoundError 打开不存在的文件
ValueError 值类型对但内容不行(int("abc"))
TypeError 类型不匹配("a" + 1)
KeyError 字典里没有这个键
IndexError 下标越界

二、try/except:铺好安全气垫

try:
    a = int(input("被除数: "))
    b = int(input("除数: "))
    print("商 =", a // b)
except ZeroDivisionError:
    print("除数不能是 0")
except ValueError:
    print("请输入整数")
else:
    print("计算完成")        # 没出异常才走
finally:
    print("(本次计算结束)")  # 无论成败都走——收尾专用

四块的分工:try 装【可能出事的代码】;except 按类型分别接住(精准处理);else 装【没出事才做的事】;finally 装无论如何都要做的收尾(关文件、还资源——回收树莓派课第十五章 finally 停车的同款纪律:不写 finally,车会带着最后的速度一直跑)。

异常也是对象:except ZeroDivisionError as e: 把它接住查看,e 里有错误信息——日志里常打它。

三、主动报警:raise

自己写的函数发现参数不对,也可以主动弹异常让调用方处理:

def divide(a, b):
    if b == 0:
        raise ValueError("除数不能为 0")   # 主动报警
    return a / b

try:
    print(divide(10, 0))
except ValueError as e:
    print("接住:", e)       # 接住: 除数不能为 0

这比返回 -1 之类的【错误码】强:错误码会被调用方忘查,异常不接住就崩——想装看不见都难。

四、动手 Demo:除零与文件双保险

def safe_div(a, b):
    try:
        return a // b
    except ZeroDivisionError:
        return None          # 或 raise 更明确——按业务定

print(safe_div(10, 3), safe_div(10, 0))   # 3 None

try:
    open("不存在的文件.txt", encoding="utf-8")
except FileNotFoundError as e:
    print("接住:", type(e).__name__)      # 接住: FileNotFoundError
finally:
    print("收尾动作照跑")

看到什么算成功:3 None、接住: FileNotFoundError、收尾动作照跑。

读结果:第一行展示【接住后自行决定返回什么】;后两行展示【按类型精准接住】与【finally 永远执行】——气垫铺在哪,程序就崩在哪之外。

五、30 秒实验

跑 int("abc") 看弹出的 ValueError 全文;再用 try: int("abc")\nexcept ValueError as e: print(e) 把它接住——同一次出错,一次崩、一次优雅。30 秒体会异常的两面。

六、常见误区

  1. 【裸 except 一网打尽最省事】——危险。except: 会连 KeyboardInterrupt、拼写错误的变量名都吞掉,真正的 bug 被气垫藏住——按类型接,最次也接 Exception。
  2. 【try 包得越多越安全】——反了。try 块一大,报错时不知道哪行出的事;只包可能出事的那几行,定位才快。
  3. 【拿异常当流程控制】——错。循环判断该用 if(可预期的正常分支),异常是【预料之外】的警报——用异常做常规跳转既慢又难读。
自测三问(点开对答案) 1. **try/except/else/finally 四块各干什么?** 答:try 装可能出事的代码;except 按类型接住处理;else 在没出事时执行;finally 无论成败都执行(收尾资源专用)。 2. **为什么 raise 比返回错误码好?** 答:异常不接住就崩,调用方想忽略都难;错误码极易被忘查,静默吞掉后患无穷。 3. **为什么不要写裸 except?** 答:它会吞掉所有异常(包括真正要暴露的 bug 与中断信号),错误被气垫藏住、无从排查——按类型精准接住。

相关笔记