楼层: 首页/ 软件技术/ Python 基础/ 异常处理:别让程序一句话崩
7

异常处理:别让程序一句话崩

try / except / with

程序跑飞了不可怕,可怕的是它"啪"一下红字退出,用户一脸懵。异常处理就是"提前告诉 Python:出事了走哪条退路"。写得好的程序,遇到错误会提示、会重试、会优雅地收尾。

try / except / else / finally

try: n = int(input("输入一个数字:")) result = 100 / n except ValueError: print("你输入的不是数字!") except ZeroDivisionError: print("不能除以 0!") else: print(f"100 / {n} = {result}") # 没出错才走这里 finally: print("无论如何都执行(关文件、关连接)")

常见异常类型

异常什么时候炸
ValueErrorint("abc") 这种类型对但值不对。
TypeError类型不对,比如 "a" + 1。
KeyError字典取了不存在的键。
IndexError列表下标越界。
FileNotFoundError打开不存在的文件。
ZeroDivisionError除以 0。

自定义异常与 raise

class AuthError(Exception): """登录失败时抛这个错""" pass def login(user, pwd): if user != "admin": raise AuthError("用户名不对") if pwd != "123456": raise AuthError("密码不对") try: login("root", "xxx") except AuthError as e: print("登录失败:", e) # 登录失败: 用户名不对

异常链:raise ... from 保留"案中案"

真实项目里,一个异常往往是"连锁反应":读文件失败是因为磁盘满,磁盘满又因为写日志撑爆了盘。如果直接 raise MyError("炸了"),原始错误就被丢掉了,排查时一脸懵。异常链就是把"上一个错误"拴在新错误上,让解释器把两层都打印出来。

论两种拴法:隐式 vs 显式

大白话:你在 except 里又 raise,Python 会自动把原异常存进新异常的 __context__(隐式链)。如果你想明确表达"我就是因为这个才报那个错",用 raise 新错 from 原错,它会把链挂到 __cause__,打印时更明确。

raise ... from:把根因一起抛出去

class ConfigError(Exception): pass def load_config(path): try: with open(path) as f: return f.read() except FileNotFoundError as e: # 业务上我们管这叫"配置坏了",但别忘了根因是文件不存在 raise ConfigError(f"配置文件 {path} 无法读取") from e try: load_config("nope.ini") except ConfigError as e: print("根因:", e.__cause__) # 根因: [Errno 2] No such file...

应用场景:分层架构里特别好用——底层报 数据库连接失败,上层包装成 ServiceUnavailable 返回给调用方,但 from e 让运维在日志里仍能看到"其实是连不上 10.0.0.5:5432"。

防坑:想断开链条用 ... from None

如果你故意不想暴露底层细节(比如对外 API,不想把数据库报错泄露给客户端),用 raise 新错 from None,就能把隐式链掐断,只留下你想让对方看到的那句错误。

练习:异常链小测(点开对答案)

问:在 except 块里只写 raise NewError(),原始异常会丢吗?
答:不会完全丢——Python 会自动放进 __context__,traceback 里仍有 "During handling of the above exception, another exception occurred"。区别是:from e 是显式因果(显示 "The above exception was the direct cause"),from None 才是真的切断。

with 语句:自动收尾

论为什么要 with

打开文件、拿锁、连数据库——这些操作用完必须"收尾"(关文件、放锁)。你忘了关,资源就泄漏。with 语句保证:不管正常结束还是中途报错,收尾一定执行。这叫"上下文管理器"。

# 不用 with:得自己记得 f.close(),中间报错就漏关 f = open("a.txt", "w") f.write("hi") f.close() # 用 with:缩进块结束自动关,哪怕报错 with open("a.txt", "w", encoding="utf-8") as f: f.write("hi") # 出了这个缩进,文件已经关好了

自己写上下文管理器

两种写法:用类实现 __enter__/__exit__,或者用 contextlib.contextmanager 装饰器(更简洁)。

from contextlib import contextmanager import time @contextmanager def timer(name): start = time.perf_counter() yield # yield 之前是 __enter__,之后是 __exit__ cost = time.perf_counter() - start print(f"[{name}] 耗时 {cost:.3f}s") with timer("跑循环"): for _ in range(100000): pass # [跑循环] 耗时 0.008s

类型提示 typing:让代码更稳

from typing import List, Dict, Optional def greet_all(names: list[str]) -> str: return "、".join(names) def find_user(uid: int) -> Optional[dict]: # 可能返回字典,也可能返回 None return db.get(uid) if db.exists(uid) else None # 配合 mypy(pip install mypy)做静态检查 # mypy your_code.py ← 提前揪出类型错误