Python · 5 分钟阅读
try / except 异常处理
Python 用 异常(Exception) 表示程序运行中的错误:除以零、找不到文件、网络超时、字典取不到 key……捕获异常能避免程序崩溃;抛出异常能让错误一路传到能被处理的地方。
本页是完整版。要看速查卡,看最后一节「速查表」。
1. 异常是怎么传播的?
当异常没有被捕获时,程序会立即停止运行并打印栈跟踪(traceback),告诉你出错的位置。栈跟踪在开发期非常有用——直接看最末尾的
xxxError和出错的文件 + 行号就能定位。
Traceback (most recent call last):
File "main.py", line 5, in <module>
do_something()
File "lib.py", line 12, in do_something
return 1 / 0
ZeroDivisionError: division by zero
Python 内置异常被组织成层次结构,最顶层是
BaseException;通常你处理Exception即可。完整列表见 https://docs.python.org/3/library/exceptions.html。
2. 捕获异常:基本结构
try:
...
except <ErrorName>:
...
def get_age() -> int:
while True:
try:
n = int(input("how old are you? "))
return n
except ValueError:
print("Please enter an integer value.")
要点:
try块里只放可能出错的代码,范围越具体越好。- 发生异常时,try 块剩余代码被跳过,立即跳到对应的
except。 - 如果 try 块没抛异常,整个
except块被跳过。
3. 捕获多种异常
def convert_int(s: str, base: int) -> int:
try:
return int(s, base)
except (ValueError, TypeError) as e: # 一并处理
return "error"
def convert_int2(s, base):
try:
return int(s, base)
except ValueError:
return "value error"
except TypeError:
return "type error"
一个
try可以有多个except;精确捕获比裸except好得多。
4. 捕获所有异常(谨慎)
def convert_int3(s, base):
try:
return int(s, base)
except Exception: # 不要 except: 这种写法太宽
return "error"
❌
except:还会捕获KeyboardInterrupt和SystemExit,会让 Ctrl-C 失效。除非真要拦所有,至少写成except Exception。
5. else / finally
try:
f = open("data.txt", encoding="utf-8")
data = f.read()
except FileNotFoundError:
print("file not found")
else:
# 只在没有异常时执行
print(f"read {len(data)} bytes")
finally:
# 不管有没有异常都执行(清理资源)
f.close()
finally 一定会执行——非常适合放 关闭文件、释放锁、关闭 socket 之类的清理代码。
更现代的写法是用 with:
with open("data.txt", encoding="utf-8") as f:
data = f.read()
# 退出 with 时 f 自动 close,不需要 try/finally
6. 主动抛出异常:raise
def set_age(age: int) -> None:
if not (0 < age <= 150):
raise ValueError("age must be in (0, 150]")
self._age = age
抛出原异常 + 上下文:
try:
parse(blob)
except JSONDecodeError as e:
raise ValueError("解析失败") from e
抑制链路(不保留原异常):
try:
...
except ValueError:
raise RuntimeError("bad input") from None
想"不真正抛,只是把异常对象存起来":
try: ... except ValueError as e: err = e # 留待稍后处理
7. 自定义异常
class AppError(Exception):
"""所有本应用异常的基类。"""
class ConfigNotFound(AppError):
pass
raise ConfigNotFound(f"missing config: {name}")
自定义异常继承
Exception(或具体子类);基类统一一个 能让上层except AppError一把兜住。
8. 异常链
try:
int("abc")
except ValueError as e:
raise RuntimeError("bad input") from e # 显示链路
# RuntimeError: bad input
# The above exception was the direct cause of the following:
# ValueError: invalid literal for int() with base 10: 'abc'
用
from None可以抑制链路。
9. 异常 vs. 校验
| 选什么 | 适用场景 |
|---|---|
| 异常 | 预期外 的情况(网络断、文件没、OOM) |
| 显式校验 | 业务流程里正常分支(表单输入不合规) |
业务流程里不要用异常走主路——异常是给"出错"用的,不是给"分支"用的。
10. 实战模式
10.1 资源管理用 with(更推荐)
with open("data.txt", encoding="utf-8") as f:
data = f.read()
# 退出 with 时 f 自动 close,不需要 try/finally
10.2 重试:循环 + 退避
import time
import random
def with_retry(fn, *, tries: int = 3, base: float = 0.5):
"""最多重试 tries 次,指数退避 + 抖动。"""
for i in range(tries):
try:
return fn()
except Exception as e:
if i == tries - 1:
raise
sleep = base * (2 ** i) + random.random() * 0.1
print(f"retry {i+1}/{tries} after {sleep:.2f}s: {e}")
time.sleep(sleep)
想做得更专业:用
tenacity/backoff库。
10.3 "先看再拿"的安全字典访问
d = {"a": 1}
# 简单
val = d.get("b", default=0)
# 复杂:KeyError 当作"业务未命中",区别于真正的程序错误
try:
val = d["b"]
except KeyError:
val = default
11. 调试小技巧
traceback.print_exc():手动打印栈跟踪。log.exception("msg"):日志 + 自动带 traceback。pdb.pm():在异常点后调试。sys.exc_info():拿到(type, value, traceback),常用于钩子。
12. 常见误区
- 裸
except::连KeyboardInterrupt/SystemExit都吃掉,调试极难。 - 过于宽泛的
except Exception:吞掉了所有错误,让 bug 变沉默。 - 用异常做流程控制:比如
try: return d[k] except KeyError: return default;多数情况d.get(k, default)更直接。 - 在
except里再抛不指定原因:丢失原上下文,调试时多走弯路。 try范围太大:捕获了不该被捕获的代码,掩盖了真实问题。
速查表
程序一旦发生未捕获的异常,就会立即停止运行并打印栈跟踪——那是给开发者看的"出错位置"线索。
最小捕获
try:
n = int(input("how old are you? "))
except ValueError:
print("Please enter an integer value.")
捕获多种异常
def to_int(s, base):
try:
return int(s, base)
except (ValueError, TypeError):
return "error"
# 或者分别处理
def to_int2(s, base):
try:
return int(s, base)
except ValueError:
return "value error"
except TypeError:
return "type error"
清理操作:finally
def invert(x):
try:
return 1 / x
except ZeroDivisionError:
return "error"
finally:
print(f"invert({x}) done")
主动抛出
def set_age(age: int) -> None:
if not (0 < age <= 150):
raise ValueError("age must be in (0, 150]")
带上下文:
try:
parse(blob)
except JSONDecodeError as e:
raise ValueError("解析失败") from e # 显式链路
抑制链路:
try:
...
except ValueError:
raise RuntimeError("bad input") from None
模式一览
| 模式 | 说明 |
|---|---|
try / except |
捕获异常 |
except (A, B) |
一次捕获多种 |
except A as e: |
拿到异常对象 |
else: |
try 块没出错时执行 |
finally: |
不管有没有异常都执行 |
raise X(...) |
主动抛出 |
raise X from e |
抛出 + 保留原异常链路 |
raise X from None |
抛出 + 隐藏原异常 |
with open(...) as f: |
自动关闭(替代 try/finally) |
except Exception as e: |
兜底捕获 |
try / except 在循环里 |
"重试 N 次"模式 |
13. 小结
- 异常是 Python 错误处理的核心机制;合理使用能写出健壮程序。
- 优先用
with管理资源;try / except / finally是补充。 - 精准捕获特定异常,别用裸
except:。 - 自定义异常统一继承一个项目级基类,便于上层统一处理。
- 重试、异常链、自定义异常是三大进阶武器。