
引言:为什么需要异常处理?
在前面的课程中,我们编写的程序都是"理想状态"下运行的。但现实中,程序经常会遇到各种意外情况:
- 用户输入了错误的数据
- 文件不存在
- 网络连接失败
- 除以零
如果没有异常处理,程序会直接崩溃。有了异常处理,我们可以优雅地处理这些错误,让程序继续运行或给出友好的提示。
异常处理是编写健壮程序的关键,掌握了异常处理,你的程序就能应对各种意外情况了。
一、try/except语句
1. 什么是异常
异常是程序运行时发生的错误。当Python遇到无法处理的情况时,会抛出一个异常。
python
# 除以零
print(10 / 0) # ZeroDivisionError: division by zero
# 访问不存在的变量
print(x) # NameError: name 'x' is not defined
# 类型错误
print("10" + 10) # TypeError: can only concatenate str (not "int") to str
2. 基本的try/except语法
python
# 基本语法
try:
# 可能出错的代码
代码块
except 异常类型:
# 发生异常时执行的代码
处理代码
# 示例
try:
result = 10 / 0
except ZeroDivisionError:
print("错误:不能除以零!")
print("程序继续运行...")
输出:
erlang
错误:不能除以零!
程序继续运行...
3. 捕获异常信息
python
# 获取异常信息
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"发生错误:{e}")
print(f"错误类型:{type(e)}")
输出:
kotlin
发生错误:division by zero
错误类型:<class 'ZeroDivisionError'>
4. 捕获多种异常
python
# 捕获多种异常
try:
num = int(input("请输入一个数字:"))
result = 10 / num
print(f"结果是:{result}")
except ValueError:
print("错误:请输入有效的数字!")
except ZeroDivisionError:
print("错误:不能除以零!")
5. 捕获所有异常
python
# 捕获所有异常(不推荐)
try:
result = 10 / 0
except Exception as e:
print(f"发生错误:{e}")
# 更安全的做法:捕获具体异常
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"除以零错误:{e}")
except ValueError as e:
print(f"值错误:{e}")
💡 提示 :尽量避免使用
except Exception捕获所有异常,这样会掩盖真正的错误。
6. try/except/else/finally
python
# 完整语法
try:
# 可能出错的代码
num = int(input("请输入一个数字:"))
result = 10 / num
except ValueError:
# 发生ValueError时执行
print("错误:请输入有效的数字!")
except ZeroDivisionError:
# 发生ZeroDivisionError时执行
print("错误:不能除以零!")
else:
# 没有异常时执行
print(f"计算成功,结果是:{result}")
finally:
# 无论是否发生异常都会执行
print("程序执行完毕")
💡 提示:
else:只在没有异常时执行finally:无论是否发生异常都会执行,常用于清理资源
7. 实际应用示例
python
def read_file(filename):
"""安全地读取文件"""
try:
with open(filename, "r", encoding="utf-8") as f:
content = f.read()
return content
except FileNotFoundError:
print(f"错误:文件 '{filename}' 不存在")
return None
except PermissionError:
print(f"错误:没有权限读取文件 '{filename}'")
return None
except Exception as e:
print(f"发生未知错误:{e}")
return None
# 测试
content = read_file("test.txt")
if content:
print(f"文件内容:{content[:50]}...")
二、常见异常类型
1. 常见异常一览表
| 异常类型 | 说明 | 示例 |
|---|---|---|
SyntaxError |
语法错误 | print("hello" |
NameError |
变量未定义 | print(x) |
TypeError |
类型错误 | "10" + 10 |
ValueError |
值错误 | int("abc") |
IndexError |
索引错误 | lst[10] |
KeyError |
键错误 | dict["key"] |
AttributeError |
属性错误 | obj.nonexistent |
ZeroDivisionError |
除以零 | 10 / 0 |
FileNotFoundError |
文件不存在 | open("no.txt") |
ImportError |
导入错误 | import xxx |
IOError |
输入输出错误 | 文件读写错误 |
KeyboardInterrupt |
用户中断 | Ctrl+C |
2. SyntaxError(语法错误)
python
# 语法错误在代码运行前就会被检测到
# print("hello" # 缺少右括号
# if True # 缺少冒号
# print("yes")
3. NameError(名称错误)
python
# 使用未定义的变量
# print(x) # NameError: name 'x' is not defined
# 解决方法:先定义变量
x = 10
print(x) # 输出:10
4. TypeError(类型错误)
python
# 类型不匹配
# print("年龄:" + 20) # TypeError
# 解决方法:类型转换
print("年龄:" + str(20)) # 输出:年龄:20
# 或者使用格式化字符串
age = 20
print(f"年龄:{age}") # 输出:年龄:20
5. ValueError(值错误)
python
# 转换失败
# num = int("abc") # ValueError
# 解决方法:使用try/except
try:
num = int("abc")
except ValueError:
print("无法转换为整数")
num = 0 # 设置默认值
6. IndexError(索引错误)
python
# 索引超出范围
lst = [1, 2, 3]
# print(lst[10]) # IndexError
# 解决方法:检查索引
index = 10
if 0 <= index < len(lst):
print(lst[index])
else:
print(f"索引 {index} 超出范围")
7. KeyError(键错误)
python
# 访问不存在的键
d = {"name": "小明", "age": 20}
# print(d["gender"]) # KeyError
# 解决方法1:使用get()
print(d.get("gender", "未知")) # 输出:未知
# 解决方法2:先检查
if "gender" in d:
print(d["gender"])
else:
print("键不存在")
8. AttributeError(属性错误)
python
# 访问不存在的属性
class Person:
def __init__(self, name):
self.name = name
p = Person("小明")
# print(p.age) # AttributeError
# 解决方法:使用hasattr()检查
if hasattr(p, "age"):
print(p.age)
else:
print("属性不存在")
9. ZeroDivisionError(除以零错误)
python
# 除以零
# result = 10 / 0 # ZeroDivisionError
# 解决方法:检查除数
divisor = 0
if divisor != 0:
result = 10 / divisor
else:
print("除数不能为零")
result = None
10. FileNotFoundError(文件不存在)
python
# 文件不存在
# with open("no_such_file.txt", "r") as f:
# content = f.read() # FileNotFoundError
# 解决方法:使用try/except
try:
with open("no_such_file.txt", "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
print("文件不存在")
content = ""
三、自定义异常
1. 为什么需要自定义异常
有时候,内置的异常类型不能满足需求,我们需要定义自己的异常类型。
python
# 示例:年龄验证
class AgeError(Exception):
"""年龄错误异常"""
pass
def set_age(age):
if age < 0:
raise AgeError("年龄不能为负数")
if age > 150:
raise AgeError("年龄不能超过150岁")
return age
# 测试
try:
age = set_age(-5)
except AgeError as e:
print(f"错误:{e}")
2. 创建自定义异常类
python
# 自定义异常类
class MyError(Exception):
"""自定义异常基类"""
pass
class ValidationError(MyError):
"""验证错误异常"""
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"字段 '{field}' 验证失败:{message}")
class NotFoundError(MyError):
"""未找到异常"""
def __init__(self, resource, id):
self.resource = resource
self.id = id
super().__init__(f"{resource} (ID: {id}) 未找到")
# 使用自定义异常
def validate_user(data):
if "name" not in data:
raise ValidationError("name", "不能为空")
if "age" not in data:
raise ValidationError("age", "不能为空")
if data["age"] < 0:
raise ValidationError("age", "不能为负数")
return True
# 测试
try:
validate_user({"name": "小明"})
except ValidationError as e:
print(f"验证错误:{e}")
print(f"字段:{e.field}")
print(f"消息:{e.message}")
3. 异常层次结构
python
# Python异常层次结构
# BaseException
# ├── SystemExit
# ├── KeyboardInterrupt
# ├── GeneratorExit
# └── Exception
# ├── ArithmeticError
# │ ├── ZeroDivisionError
# │ ├── OverflowError
# │ └── FloatingPointError
# ├── AttributeError
# ├── ImportError
# ├── IndexError
# ├── KeyError
# ├── NameError
# ├── TypeError
# ├── ValueError
# └── ...
# 自定义异常应该继承Exception类
class CustomError(Exception):
"""自定义异常"""
pass
4. 异常链
python
# 异常链:在捕获异常时抛出新的异常
class DatabaseError(Exception):
"""数据库错误"""
pass
def query_database(sql):
try:
# 模拟数据库查询
result = 10 / 0 # 模拟错误
except ZeroDivisionError as e:
# 抛出新的异常,保留原始异常信息
raise DatabaseError(f"数据库查询失败:{sql}") from e
# 测试
try:
query_database("SELECT * FROM users")
except DatabaseError as e:
print(f"数据库错误:{e}")
print(f"原始异常:{e.__cause__}")
5. 异常组(Python 3.11+)
python
# Python 3.11+ 支持异常组
# 用于同时处理多个异常
# 示例:并行任务中的异常处理
async def process_tasks():
tasks = [task1(), task2(), task3()]
try:
await asyncio.gather(*tasks)
except ExceptionGroup as eg:
for e in eg.exceptions:
print(f"任务失败:{e}")
四、异常处理最佳实践
1. 只捕获必要的异常
python
# ❌ 不推荐:捕获所有异常
try:
result = 10 / num
except Exception:
pass # 掩盖了真正的错误
# ✅ 推荐:捕获具体异常
try:
result = 10 / num
except ZeroDivisionError:
print("除数不能为零")
except TypeError:
print("类型错误")
2. 不要忽略异常
python
# ❌ 不推荐:忽略异常
try:
do_something()
except Exception:
pass # 错误被忽略了
# ✅ 推荐:至少记录异常
import logging
try:
do_something()
except Exception as e:
logging.error(f"发生错误:{e}")
# 可以选择重新抛出或处理
3. 使用finally清理资源
python
# ✅ 推荐:使用finally确保资源被释放
file = None
try:
file = open("data.txt", "r", encoding="utf-8")
content = file.read()
# 处理内容
finally:
if file:
file.close() # 确保文件被关闭
# 更好的方式:使用with语句
with open("data.txt", "r", encoding="utf-8") as file:
content = file.read()
4. 使用上下文管理器
python
from contextlib import contextmanager
@contextmanager
def open_file(filename, mode="r"):
"""文件上下文管理器"""
file = None
try:
file = open(filename, mode, encoding="utf-8")
yield file
finally:
if file:
file.close()
# 使用
with open_file("data.txt", "r") as f:
content = f.read()
5. 异常处理策略
python
# 策略1:快速失败
def divide(a, b):
if b == 0:
raise ValueError("除数不能为零")
return a / b
# 策略2:返回默认值
def safe_divide(a, b, default=0):
try:
return a / b
except ZeroDivisionError:
return default
# 策略3:记录并继续
import logging
def process_data(data_list):
results = []
for data in data_list:
try:
result = process(data)
results.append(result)
except Exception as e:
logging.error(f"处理数据失败:{data},错误:{e}")
continue
return results
五、实战练习
练习1:安全的用户输入
python
def get_valid_age():
"""获取有效的年龄输入"""
while True:
try:
age = int(input("请输入年龄(0-150):"))
if 0 <= age <= 150:
return age
else:
print("年龄必须在0-150之间")
except ValueError:
print("请输入有效的整数")
# 测试
age = get_valid_age()
print(f"您输入的年龄是:{age}")
练习2:文件处理器
python
import json
from pathlib import Path
class FileProcessor:
"""文件处理器"""
def __init__(self, filename):
self.filename = Path(filename)
def read_json(self):
"""读取JSON文件"""
try:
with self.filename.open("r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
print(f"文件不存在:{self.filename}")
return {}
except json.JSONDecodeError as e:
print(f"JSON格式错误:{e}")
return {}
def write_json(self, data):
"""写入JSON文件"""
try:
with self.filename.open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"写入失败:{e}")
return False
# 测试
processor = FileProcessor("config.json")
data = processor.read_json()
data["version"] = "1.0.0"
processor.write_json(data)
练习3:自定义异常体系
python
class AppError(Exception):
"""应用错误基类"""
def __init__(self, message, code=None):
super().__init__(message)
self.code = code
class ValidationError(AppError):
"""验证错误"""
def __init__(self, field, message):
super().__init__(f"字段 '{field}' 验证失败:{message}", code="VALIDATION_ERROR")
self.field = field
class NotFoundError(AppError):
"""未找到错误"""
def __init__(self, resource, id):
super().__init__(f"{resource} (ID: {id}) 未找到", code="NOT_FOUND")
self.resource = resource
self.id = id
class AuthenticationError(AppError):
"""认证错误"""
def __init__(self, message="认证失败"):
super().__init__(message, code="AUTH_ERROR")
# 使用
def create_user(data):
if "name" not in data:
raise ValidationError("name", "不能为空")
if "email" not in data:
raise ValidationError("email", "不能为空")
return {"id": 1, **data}
# 测试
try:
user = create_user({"name": "小明"})
except ValidationError as e:
print(f"验证错误:{e}")
print(f"错误代码:{e.code}")
print(f"字段:{e.field}")
except AppError as e:
print(f"应用错误:{e}")
print(f"错误代码:{e.code}")
练习4:重试机制
python
import time
import random
def retry(max_attempts=3, delay=1, exceptions=(Exception,)):
"""重试装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_attempts:
print(f"第 {attempt} 次尝试失败,放弃重试")
raise
print(f"第 {attempt} 次尝试失败:{e},{delay}秒后重试...")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=1, exceptions=(ConnectionError,))
def unstable_api_call():
"""模拟不稳定的API调用"""
if random.random() < 0.7: # 70%概率失败
raise ConnectionError("连接失败")
return {"status": "success"}
# 测试
try:
result = unstable_api_call()
print(f"调用成功:{result}")
except ConnectionError as e:
print(f"最终失败:{e}")
六、常见问题与排错
问题1:异常被忽略
原因 :使用空的except块 解决方法:至少记录异常信息
python
# ❌ 错误
try:
do_something()
except:
pass # 异常被忽略了
# ✅ 正确
try:
do_something()
except Exception as e:
print(f"发生错误:{e}")
# 或者记录日志
# logging.error(f"发生错误:{e}")
问题2:异常处理顺序
原因 :父类异常在前,子类异常在后 解决方法:子类异常在前,父类异常在后
python
# ❌ 错误
try:
result = 10 / 0
except Exception: # 父类在前
print("发生异常")
except ZeroDivisionError: # 子类在后,永远不会执行
print("除以零")
# ✅ 正确
try:
result = 10 / 0
except ZeroDivisionError: # 子类在前
print("除以零")
except Exception: # 父类在后
print("其他异常")
问题3:finally中的return
原因 :finally中的return会覆盖try/except中的return 解决方法:避免在finally中使用return
python
# ❌ 不推荐
def test():
try:
return 1
finally:
return 2 # 会覆盖try中的return
print(test()) # 输出:2
# ✅ 推荐:避免在finally中return
def test():
try:
return 1
finally:
pass # 只做清理工作
print(test()) # 输出:1
七、本篇小结
今天,我们完成了以下学习内容:
-
try/except语句:
- 基本的异常捕获语法
- 捕获多种异常
- try/except/else/finally完整语法
-
常见异常类型:
- SyntaxError、NameError、TypeError
- ValueError、IndexError、KeyError
- AttributeError、ZeroDivisionError
- FileNotFoundError等
-
自定义异常:
- 创建自定义异常类
- 异常层次结构
- 异常链
- 异常组(Python 3.11+)
-
异常处理最佳实践:
- 只捕获必要的异常
- 不要忽略异常
- 使用finally清理资源
- 异常处理策略
请记住这三个结论:
- 异常处理让程序更健壮:使用try/except捕获和处理异常
- 捕获具体异常 :避免使用
except Exception捕获所有异常 - 自定义异常提高可读性:为特定场景创建自定义异常类
下期预告
在第11期中,我们将学习Python的面向对象编程基础,包括:
- 类和对象的概念
- 属性和方法的定义
__init__构造方法- 实例属性和类属性
敬请期待!
✍坚持原创,求关注,点赞,收藏