1. 异常是什么? #
- 异常(exception) 是程序运行中发生的错误,会以抛出的方式中断当前代码流。
- 若无人捕获,异常会沿调用栈向上传递,最终导致程序崩溃或返回 500。
- 用
try / except 可捕获可预期的错误,给出友好提示而不是裸堆栈。
- 捕获后常配合记日志、返回统一错误响应,并保证文件、连接等资源正确释放。
- 全栈项目里:参数非法、用户不存在、文件缺失等都应通过异常或校验结果明确表达,而不是静默失败。
2. 基本语法 #
try 块写可能出错的代码;except 异常类型: 块写捕获后的处理逻辑。
- 可写多个
except,Python 从上到下匹配,命中第一个符合条件的分支。
- 先写具体异常(如
ValueError),再写宽泛的(如 Exception),避免具体错误被笼统分支吞掉。
- 业务层常把不同异常映射为不同 HTTP 状态码或错误码,见下文 Web 示例。
try:
user_input = input("请输入一个数字: ")
age = int(user_input)
result = 100 / age
print(result)
except ValueError:
print("请输入数字")
except ZeroDivisionError:
print("不能为 0")
import logging
logger = logging.getLogger(__name__)
class UserNotFoundError(Exception):
pass
def fetch_user(user_id):
if user_id == 1:
return {"id": 1, "name": "用户A"}
else:
raise UserNotFoundError()
def get_user(user_id):
try:
data = fetch_user(user_id)
return {"data": data}, 200
except UserNotFoundError:
return {"error": "用户不存在"}, 404
except Exception:
logger.exception("未知错误")
return {"error": "服务器错误"}, 500
print(get_user(1))
print(get_user(2))
2.1 else 与 finally #
else:仅在 try 未发生异常时执行,适合「尝试成功后才做的后续步骤」。
finally:无论是否异常都会执行,常用于释放资源;现代 Python 更推荐 with 管理文件等。
- 完整结构为
try → except → else → finally,else 可省略。
- 业务代码里打开文件优先
with open(...),比手写 try/finally 更简洁、不易漏关。
try:
f = open("data.txt", encoding="utf-8")
except FileNotFoundError:
print("文件不存在")
else:
print("仅无异常时执行", f.read())
finally:
print("无论是否异常都会执行")
3. 常见内置异常 #
- Python 内置多种异常类型,名称通常以
Error 结尾,便于 except 精确捕获。
- 开发时应熟悉下表中最常见的几类,读 traceback 时能快速定位是参数、类型还是资源问题。
- 接口层不要把原始堆栈直接返回给用户,而应转成 400/404/500 与统一 JSON 错误体。
- 字典取值优先考虑
get,减少 KeyError;类型转换配合 try/except ValueError 或先校验。
| 异常 |
典型原因 |
ValueError |
值不合法,如 int("abc") |
TypeError |
类型不对,如 "1" + 1 |
KeyError |
字典缺少键 |
IndexError |
下标越界 |
FileNotFoundError |
文件不存在 |
PermissionError |
无权限 |
AttributeError |
属性/方法不存在 |
ZeroDivisionError |
除零 |
4. 主动抛出:raise #
- 用
raise 在发现不合法条件时主动抛出异常,而不是返回 None 让调用方猜。
raise ValueError("说明") 常用于参数校验失败(如 ID ≤ 0、格式不对)。
raise UserNotFoundError(...) 常用于业务规则不满足(如用户不存在)。
- 抛出的消息应简短明确,便于日志与 API 响应;敏感内部细节不要写进
str(e) 返回给前端。
class DB:
def __init__(self):
self.users = {1: {"id": 1, "name": "Alice"}, 2: {"id": 2, "name": "Bob"}}
def find(self, user_id):
return self.users.get(user_id)
class UserNotFoundError(Exception):
pass
db = DB()
def get_user(user_id: int):
if user_id <= 0:
raise ValueError("用户 ID 必须大于 0")
user = db.find(user_id)
if not user:
raise UserNotFoundError(f"用户 {user_id} 不存在")
return user
5. 在 except 里重新 raise #
- 在
except 块里写 raise(不带异常对象)表示捕获后原样继续向上抛。
- 典型用法:本层记日志或做清理,仍把异常交给上层统一处理。
- 与
raise OtherError(...) from e 不同: bare raise 保留原异常类型与堆栈,不包装成新异常。
- 不要
except Exception: pass 吞掉错误;至少记日志再 raise,或转成业务异常。
import logging
class DatabaseError(Exception):
pass
def process(data):
try:
raise DatabaseError("数据库操作失败")
except DatabaseError:
raise
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.ERROR)
raw = "测试数据"
try:
process(raw)
except DatabaseError:
logger.error("数据库失败", exc_info=True)
6. 异常链 #
- 底层库抛
OSError、ConnectionError 时,业务层可包装为 BusinessError,对用户暴露统一语义。
- 写法:
raise BusinessError("下单失败") from e,from e 保留原始原因。
- 排查时查看
e.__cause__ 或完整 traceback,可看到底层真实错误。
- 异常链避免「只剩一句业务提示、却丢了数据库/网络失败原因」的排障困境。
class BusinessError(Exception):
pass
def order_service():
try:
raise OSError("库存系统连接失败")
except OSError as e:
raise BusinessError("下单失败") from e
try:
order_service()
except BusinessError as e:
print("捕获到业务异常:", e)
print("底层原因:", e.__cause__)
7. 自定义异常 #
- 自定义异常类继承
Exception(或项目基类 AppError),用于区分业务错误与系统错误。
- 调用方可用
except UserNotFoundError 精确处理,而不必解析字符串消息。
- 可在
__init__ 里携带 order_id、reason 等字段,供日志与 API 使用。
- 项目宜定义少量有层次的异常(基类 + 几个子类),避免异常类爆炸、难以维护。
class AppError(Exception):
pass
class UserNotFoundError(AppError):
pass
class PaymentFailedError(AppError):
def __init__(self, order_id: str, reason: str):
self.order_id = order_id
self.reason = reason
super().__init__(f"Order {order_id} payment failed: {reason}")
try:
raise UserNotFoundError("用户不存在")
except UserNotFoundError as e:
print(f"Caught UserNotFoundError: {e}")
try:
raise PaymentFailedError("12345", "余额不足")
except PaymentFailedError as e:
print(f"Caught PaymentFailedError: {e}, order_id: {e.order_id}, reason: {e.reason}")
8. Web 中的处理方式 #
- 分层习惯:service / 业务层抛
UserNotFoundError、ValueError 等;视图 / 入口层统一 try/except 映射 HTTP 状态码。
UserNotFoundError → 404;参数非法 ValueError → 400;未预料错误 → 500 并记日志。
- 响应体建议统一形如
{"error": "消息"},不要把 Python 堆栈返回给浏览器。
- Django REST framework、FastAPI 等还可通过全局异常处理器集中映射,减少每个视图重复写
try/except。
class UserNotFoundError(Exception):
pass
def get_user(user_id: int):
if not isinstance(user_id, int) or user_id <= 0:
raise ValueError("user_id 必须为正整数")
if user_id != 1:
raise UserNotFoundError(f"用户 {user_id} 未找到")
return {"id": 1, "name": "张三"}
def get_user_api(user_id: int):
try:
return {"data": get_user(user_id)}
except UserNotFoundError as e:
return {"error": str(e)}, 404
except ValueError as e:
return {"error": str(e)}, 400
except Exception as e:
return {"error": "Internal Server Error: " + str(e)}, 500
print(get_user_api(1))
9. 项目开发要点 #
- 捕获具体异常,避免裸
except: 或 except Exception: pass 吞掉所有错误。
- 不要用异常做流程控制;正常分支用
if,异常留给真正的错误情况。
- 资源用
with 打开文件;数据库连接交给框架或连接池,少手写关连接。
- 记日志用
logging.exception(...) 或 logger.error(..., exc_info=True) 保留堆栈。
- 对外接口返回明确错误码与 message;内部实现细节、SQL、路径不要进响应体。
- 校验失败优先返回 400 与字段级提示,少把未处理堆栈暴露到前端。
10. 总结 #
- 异常处理核心:
try/except 捕获、raise 主动报错、自定义类区分业务错误。
- 资源释放优先
with;需要记录后上交用 except 里 raise 或异常链 from e。
- Web 项目把异常映射为 HTTP 状态码 与统一 JSON,堆栈只进日志。
- 下表可作语法速查。
| 语法 |
用途 |
try / except |
捕获并处理错误 |
raise |
主动报错、校验失败 |
| 自定义异常类 |
区分业务错误与系统错误 |
with |
自动释放文件等资源 |
日志 + 重新 raise |
记录后交给上层或统一处理器 |