主题
🛡 异常处理
程序运行中总会遇到各种意外——文件不存在、网络断了、用户输入了奇怪的东西。异常处理就是给程序穿上铠甲。
异常是什么?
异常(Exception)是程序运行时抛出的错误信号。不处理的后果:程序崩溃,后面代码全不跑。
python
print("程序开始")
result = 10 / 0 # ZeroDivisionError!程序在此崩溃
print("这行不会执行")try/except — 捕获异常
python
try:
num = int(input("请输入一个数字:"))
print(f"100 ÷ {num} = {100 / num}")
except ValueError:
print("不是数字!请重新运行。")
except ZeroDivisionError:
print("不能除以零!")else 和 finally
python
def read_config(path):
config = None
try:
with open(path, "r", encoding="utf-8") as f:
config = f.read()
except FileNotFoundError:
print(f"配置文件 {path} 不存在,使用默认配置")
config = "default"
else:
# try 块没出异常才执行
print(f"成功读取配置文件({len(config)} 字符)")
finally:
# 无论如何都执行(清理资源)
print("配置加载流程结束")
return config- try:可能出问题的代码
- except:出问题后怎么处理
- else:没出问题才跑(可选)
- finally:不管出不出问题都要跑(可选,常用于释放资源)
常见异常类型
| 异常 | 触发条件 | 示例 |
|---|---|---|
ValueError | 类型对但值不对 | int("abc") |
TypeError | 类型不对 | "a" + 1 |
KeyError | 字典键不存在 | d["不存在的键"] |
IndexError | 列表索引越界 | [1,2][5] |
FileNotFoundError | 文件不存在 | open("不存在.txt") |
ZeroDivisionError | 除以零 | 1 / 0 |
AttributeError | 对象没有这个属性 | "hi".fake_method() |
ImportError | 导入失败 | import 不存在的模块 |
OSError | 系统操作失败 | 权限不足、磁盘满 |
完整继承关系:BaseException → Exception → 具体异常。日常捕获用 Exception 即可。
raise — 主动抛异常
python
def set_age(age):
if age < 0:
raise ValueError(f"年龄不能为负数:{age}")
if age > 150:
raise ValueError(f"年龄不太可能超过150:{age}")
print(f"年龄设置为 {age}")
set_age(25) # OK
# set_age(-5) # 抛异常自定义异常
python
class BalanceError(Exception):
"""余额不足异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"余额 {balance} 不足,无法取款 {amount}")
def withdraw(balance, amount):
if amount > balance:
raise BalanceError(balance, amount)
return balance - amount
try:
withdraw(100, 200)
except BalanceError as e:
print(e) # 余额 100 不足,无法取款 200异常链 — from
一个异常引发另一个异常时,保留因果关系:
python
def load_data(path):
try:
with open(path, "r") as f:
return f.read()
except FileNotFoundError as e:
raise RuntimeError(f"数据文件 {path} 加载失败") from e
# 抛出 RuntimeError,但用 __cause__ 指向原始 FileNotFoundError用 from None 可以切断因果链:raise ValueError("数据无效") from None。
捕获的哲学
什么时候捕获异常?
捕获异常的目的是恢复或优雅退出,不是隐藏 bug。
- ✅ 该捕获的:文件可能不存在、网络可能断、用户输入不可控
- ❌ 不该捕获的:代码逻辑错误(类型错误、索引越界),这些应该修代码而不是藏起来
最坏的写法
python
# ❌ 什么都吞掉,出了问题你永远不知道
try:
do_something()
except:
pass # 什么也不做出 bug 时你看到的是"程序莫名其妙不 work 了",排查到吐血。
实战:健壮的文件读写 + 重试机制
python
# /opt/kb/docs/python/exceptions_demo.py
"""实战:健壮的文件读写、API调用重试"""
import time
import json
from pathlib import Path
def safe_read_json(path, default=None):
"""安全读取 JSON,文件不存在或格式错误时返回默认值"""
try:
return json.loads(Path(path).read_text(encoding="utf-8"))
except FileNotFoundError:
print(f"[警告] {path} 不存在,使用默认值")
except json.JSONDecodeError as e:
print(f"[错误] {path} JSON 格式错误:{e}")
except Exception as e:
print(f"[错误] 读取 {path} 时发生意外:{e}")
return default if default is not None else {}
def retry_call(func, max_retries=3, delay=1):
"""带重试的函数调用(适合网络请求等不稳定操作)"""
for attempt in range(1, max_retries + 1):
try:
return func()
except Exception as e:
if attempt == max_retries:
raise # 最后一次也失败了,抛出异常
print(f"第 {attempt} 次失败:{e},{delay}秒后重试...")
time.sleep(delay)
# 模拟不稳定的 API
import random
def unstable_api():
"""模拟:30% 概率失败的 API"""
if random.random() < 0.3:
raise ConnectionError("网络超时")
return {"data": "success"}
# 使用
if __name__ == "__main__":
# 1. 安全读取
config = safe_read_json("config.json", {"theme": "light"})
print(f"配置: {config}")
# 2. 带重试
try:
result = retry_call(unstable_api, max_retries=5, delay=0.5)
print(f"API 返回: {result}")
except Exception as e:
print(f"API 最终失败: {e}")常见坑
坑1:except 太宽
python
# ❌ 捕获所有异常,包括 KeyboardInterrupt(Ctrl+C)和 SystemExit
try:
risky_work()
except Exception: # 至少用 Exception,别裸写 except:
pass # 但什么都不做也很糟糕至少记录日志:except Exception as e: logger.error(f"操作失败: {e}")。
坑2:except 后面什么都不做
except: pass 是"灭迹模式"——出错了你永远不知道。至少 print() 或 logging.error()。
坑3:finally 里 return
python
def bad():
try:
return "try"
finally:
return "finally" # 覆盖了 try 的返回值!
print(bad()) # "finally" — try 的 return 被吞了finally 里的 return 会吞掉 try/except 中的 return。不要在 finally 里写 return。
练习题
- 写程序让用户输入数字做除法,捕获
ValueError和ZeroDivisionError,反复提示直到输入正确 - 自定义一个
InvalidPasswordError异常,写一个密码校验函数,要求至少 8 位且含数字 - 模拟读取 10 个文件(有的存在有的不存在),用异常处理跳过不存在的
- 实现一个
safe_divide(a, b)函数,除以零时返回None而不是崩溃 - 给
retry_call加指数退避(delay 翻倍):第1次等1秒、第2次等2秒、第3次等4秒
🛡 异常处理让程序坚不可摧。下节学 常用标准库,摸清 Python 自带的宝库。
🎯 本章要点
- else 只在 try 块没有抛出任何异常时才执行。
- finally 无论是否发生异常都会执行
- FileNotFoundError 是 Python 内置异常。
加载练习题中...