Python教程26:异常处理
“预则立,不预则废。”
程序运行时难免会遇到错误:文件不存在、网络断开、用户输入非法数据。异常处理机制让程序能够优雅地应对这些错误,而不是直接崩溃。今天我们学习Python的异常处理。
1. 什么是异常
错误vs异常
语法错误(Syntax Error):
- 代码写错了,Python无法解析
- 程序无法运行
- 例如:忘记冒号、括号不匹配
1# 语法错误示例
2if True # SyntaxError: 缺少冒号
3 print("Hello")
异常(Exception):
- 语法正确,但运行时出错
- 可以被捕获和处理
- 例如:除零、文件不存在、类型错误
1# 异常示例
2x = 1 / 0 # ZeroDivisionError: division by zero
为什么需要异常处理
没有异常处理:
1def divide(a, b):
2 return a / b
3
4result = divide(10, 0) # 程序崩溃!
5print("这行代码不会执行")
有异常处理:
1def divide(a, b):
2 try:
3 return a / b
4 except ZeroDivisionError:
5 print("错误:除数不能为0")
6 return None
7
8result = divide(10, 0)
9print("程序继续运行") # 这行会执行
异常处理的作用:
- 防止程序崩溃
- 提供友好的错误信息
- 执行清理操作(关闭文件、释放资源)
- 记录错误日志
- 优雅降级(功能不可用时提供备选方案)
2. 基本语法:try-except
1try:
2 # 可能出错的代码
3 risky_operation()
4except ExceptionType:
5 # 处理异常的代码
6 handle_error()
简单示例
1try:
2 num = int(input("请输入数字:"))
3 result = 10 / num
4 print(f"结果:{result}")
5except ValueError:
6 print("错误:请输入有效的数字")
7except ZeroDivisionError:
8 print("错误:除数不能为零")
执行流程:
- 执行try块中的代码
- 如果没有异常:跳过except块
- 如果有异常:跳到对应的except块
- except块执行完后,程序继续
3. 常见异常类型
Python内置了很多异常类,都继承自BaseException:
1# ZeroDivisionError - 除零错误
2# 当除数或模数为零时抛出
3try:
4 result = 10 / 0
5except ZeroDivisionError as e:
6 print(f"除零错误:{e}")
7
8# ValueError - 值错误
9# 函数接收了正确类型但值不合适的参数
10try:
11 num = int("abc")
12except ValueError as e:
13 print(f"值错误:{e}")
14
15# TypeError - 类型错误
16# 操作应用于不适当类型的对象
17try:
18 result = "2" + 2
19except TypeError as e:
20 print(f"类型错误:{e}")
21
22# FileNotFoundError - 文件不存在
23# 尝试打开不存在的文件时
24try:
25 with open("nonexistent.txt") as f:
26 content = f.read()
27except FileNotFoundError as e:
28 print(f"文件未找到:{e}")
29
30# KeyError - 键错误
31# 字典中不存在该键
32try:
33 d = {"name": "Alice"}
34 print(d["age"])
35except KeyError as e:
36 print(f"键不存在:{e}")
37
38# IndexError - 索引错误
39# 序列索引超出范围
40try:
41 numbers = [1, 2, 3]
42 print(numbers[10])
43except IndexError as e:
44 print(f"索引错误:{e}")
45
46# AttributeError - 属性错误
47# 对象没有该属性或方法
48try:
49 x = 5
50 x.append(1)
51except AttributeError as e:
52 print(f"属性错误:{e}")
异常层次结构
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
├── StopIteration
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── OverflowError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── ValueError
├── TypeError
└── ...
4. 捕获多个异常
方法1:多个except块
1try:
2 # 各种可能的操作
3 result = risky_operation()
4except ValueError:
5 print("值错误")
6except TypeError:
7 print("类型错误")
8except KeyError:
9 print("键错误")
方法2:一个except捕获多种异常
1try:
2 result = risky_operation()
3except (ValueError, TypeError, KeyError) as e:
4 print(f"发生错误:{e}")
方法3:捕获所有异常(不推荐)
1try:
2 result = risky_operation()
3except Exception as e: # Exception是大多数异常的基类
4 print(f"发生未知错误:{e}")
注意:
- 不要用
except:捕获所有异常(包括SystemExit、KeyboardInterrupt) - 尽量捕获具体的异常类型
Exception捕获大部分异常,但不包括系统异常
5. else子句
else块在try块没有异常时执行:
1try:
2 num = int(input("请输入数字:"))
3except ValueError:
4 print("输入无效")
5else:
6 # 只有输入有效时才执行
7 print(f"你输入的数字是:{num}")
8 result = num * 2
9 print(f"两倍是:{result}")
为什么用else:
- 分离正常代码和异常处理代码
- 让异常范围更精确
- 代码结构更清晰
1# 不好的写法
2try:
3 file = open("data.txt")
4 content = file.read()
5 data = process(content) # process也在try中
6except FileNotFoundError:
7 print("文件不存在")
8
9# 好的写法
10try:
11 file = open("data.txt")
12except FileNotFoundError:
13 print("文件不存在")
14else:
15 # 文件打开成功后的处理
16 content = file.read()
17 data = process(content)
6. finally子句
finally块无论是否发生异常都会执行:
1try:
2 file = open("data.txt")
3 content = file.read()
4except FileNotFoundError:
5 print("文件不存在")
6finally:
7 # 无论如何都会执行
8 print("清理操作")
9 if 'file' in locals():
10 file.close()
finally的用途:
- 释放资源(关闭文件、网络连接)
- 清理临时数据
- 日志记录
- 确保某些代码一定执行
执行顺序:
1try:
2 print("1. try块")
3 # raise Exception() # 取消注释测试异常情况
4 print("2. try块继续")
5except Exception:
6 print("3. except块")
7else:
8 print("4. else块")
9finally:
10 print("5. finally块")
11print("6. 后续代码")
12
13# 无异常:1 -> 2 -> 4 -> 5 -> 6
14# 有异常:1 -> 3 -> 5 -> 6
7. 抛出异常:raise
主动抛出异常
1def set_age(age):
2 """设置年龄"""
3 if age < 0:
4 raise ValueError("年龄不能为负数")
5 if age > 150:
6 raise ValueError("年龄不合理")
7 print(f"年龄设置为:{age}")
8
9try:
10 set_age(-5)
11except ValueError as e:
12 print(f"错误:{e}")
重新抛出异常
1try:
2 file = open("data.txt")
3except FileNotFoundError:
4 print("文件不存在,记录错误")
5 raise # 重新抛出同一个异常
异常链
1try:
2 result = 10 / 0
3except ZeroDivisionError as e:
4 # from用于保留原始异常
5 raise ValueError("计算失败") from e
8. 自定义异常
创建自己的异常类:
1class InvalidAgeError(Exception):
2 """
3 自定义异常:年龄无效
4 - 继承Exception或其子类
5 - 添加自定义属性和方法
6 """
7 def __init__(self, age, message="年龄无效"):
8 self.age = age
9 self.message = message
10 super().__init__(self.message)
11
12 def __str__(self):
13 return f"{self.message}:{self.age}"
14
15# 使用自定义异常
16def set_age(age):
17 if not isinstance(age, int):
18 raise TypeError("年龄必须是整数")
19 if age < 0 or age > 150:
20 raise InvalidAgeError(age, "年龄不在合理范围")
21 return age
22
23try:
24 set_age(-5)
25except InvalidAgeError as e:
26 print(f"自定义异常:{e}")
何时自定义异常:
- 业务逻辑需要特定的异常类型
- 需要携带额外信息
- 方便异常分类处理
- 提高代码可读性
9. 异常处理最佳实践
1. 具体异常优先
1# 好:具体异常
2try:
3 num = int(input())
4except ValueError:
5 print("请输入数字")
6
7# 不好:过于宽泛
8try:
9 num = int(input())
10except Exception: # 捕获所有异常
11 print("出错了")
2. 不要忽略异常
1# 不好:静默失败
2try:
3 risky_operation()
4except Exception:
5 pass # 什么都不做,错误被隐藏
6
7# 好:至少记录日志
8import logging
9
10try:
11 risky_operation()
12except Exception as e:
13 logging.error(f"操作失败:{e}")
3. 缩小try范围
1# 不好:try范围太大
2try:
3 data = load_data()
4 processed = process(data)
5 result = analyze(processed)
6 save(result)
7except ValueError:
8 print("值错误")
9
10# 好:只包含可能出错的部分
11data = load_data()
12processed = process(data)
13
14try:
15 result = analyze(processed)
16except ValueError:
17 print("分析失败")
18
19save(result)
4. 使用with管理资源
1# 不好:手动管理资源
2file = open("data.txt")
3try:
4 content = file.read()
5finally:
6 file.close()
7
8# 好:with自动管理
9with open("data.txt") as file:
10 content = file.read()
11# 自动关闭,即使有异常
10. 实用示例
文件读取with重试
1import time
2
3def read_file_with_retry(filename, max_retries=3):
4 """带重试的文件读取"""
5 for attempt in range(max_retries):
6 try:
7 with open(filename) as f:
8 return f.read()
9 except FileNotFoundError:
10 if attempt < max_retries - 1:
11 print(f"文件未找到,{attempt + 1}秒后重试...")
12 time.sleep(attempt + 1)
13 else:
14 print("达到最大重试次数")
15 raise
数据验证
1def validate_user_input(data):
2 """验证用户输入"""
3 errors = []
4
5 try:
6 age = int(data.get("age"))
7 if age < 0 or age > 150:
8 errors.append("年龄不合理")
9 except (ValueError, TypeError):
10 errors.append("年龄必须是数字")
11
12 if not data.get("name"):
13 errors.append("姓名不能为空")
14
15 if errors:
16 raise ValueError(f"输入验证失败:{', '.join(errors)}")
17
18 return True
11. 小结
今天我们学习了Python异常处理:
- 异常vs错误:语法错误vs运行时异常
- try-except:捕获和处理异常
- 常见异常:ValueError、TypeError、FileNotFoundError等
- 多异常处理:多个except、元组、Exception
- else:无异常时执行
- finally:总是执行,用于清理
- raise:主动抛出异常
- 自定义异常:继承Exception
- 最佳实践:具体异常、记录错误、缩小范围
异常处理是写健壮程序的关键,让程序能够优雅地应对各种错误情况。
练习题:
- 写一个函数,安全地将字符串转换为整数,处理所有可能的异常
- 实现一个文件复制函数,包含完整的异常处理
- 创建自定义异常类层次结构,用于用户认证系统
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
