Day 17:【99天精通Python】异常处理 - 让程序稳如泰山
本文介绍了Python异常处理的核心知识,包括: 异常概念及常见类型(如ZeroDivisionError、ValueError等) try-except基本语法及捕获特定异常的方法 else和finally子句的用途与执行顺序 使用raise主动抛出异常 自定义异常类的实现方式 两个实战练习:健壮输入器和安全文件读取 异常处理最佳实践与常见问题解答 文章通过代码示例详细演示了如何优雅处理程序运行
Day 17:【99天精通Python】异常处理 - 让程序稳如泰山
前言
欢迎来到第17天!
在编程的世界里,不出 bug 是不可能的。即便是最顶尖的程序员,也无法保证代码永远不出错。用户可能会输入非法数据,文件可能突然被删除,网络可能会断开。
如果程序一遇到错误就直接崩溃(闪退),用户体验会非常差。异常处理 (Exception Handling) 就是给程序穿上一层"防弹衣",当意外发生时,我们能优雅地捕获错误,并给出友好的提示,而不是让程序直接挂掉。
本节内容:
- 什么是异常?
try...except基本结构- 捕获多种异常
else和finally的作用- 主动抛出异常
raise - 自定义异常类
- 实战练习
一、什么是异常 (Exception)?
异常是指程序在运行过程中发生的错误,它会打断指令的正常执行。
常见的异常类型:
SyntaxError: 语法错误(通常在运行前就会报错)。NameError: 尝试访问一个未定义的变量。TypeError: 类型不匹配(例如数字 + 字符串)。ZeroDivisionError: 除数为 0。IndexError: 列表索引越界。KeyError: 字典中不存在该键。FileNotFoundError: 打开不存在的文件。
二、基本语法:try…except
我们使用 try 块包含可能出错的代码,用 except 块来处理错误。
2.1 基本结构
try:
# 可能会报错的代码
num = int(input("请输入一个数字: "))
result = 10 / num
print(f"计算结果: {result}")
except ZeroDivisionError:
# 当发生除以0错误时执行
print("错误:除数不能为0!")
except ValueError:
# 当输入的不是数字时执行
print("错误:请输入有效的整数!")
2.2 获取异常信息
有时我们需要知道具体的报错信息,可以使用 as 关键字。
try:
print(1 / 0)
except ZeroDivisionError as e:
print(f"捕获到错误: {e}") # 捕获到错误: division by zero
2.3 捕获所有异常 (慎用)
可以使用 Exception 捕获所有类型的运行时错误。但通常不推荐这样做,因为这会掩盖一些意想不到的 bug(比如变量名写错)。
try:
# 复杂逻辑
pass
except Exception as e:
print(f"发生未知错误: {e}")
三、完善结构:else 与 finally
异常处理还有两个可选的子句:else 和 finally。
else: 当try块没有发生异常时执行。finally: 无论是否发生异常,最终都会执行(常用于清理资源,如关闭文件、断开数据库)。
def division_test(x, y):
try:
result = x / y
except ZeroDivisionError:
print("Error: 除数不能为0")
else:
print(f"Success: 结果是 {result}")
finally:
print("--- 执行完毕 ---")
print("第一次测试:")
division_test(10, 2)
print("\n第二次测试:")
division_test(10, 0)
运行结果:
第一次测试:
Success: 结果是 5.0
--- 执行完毕 ---
第二次测试:
Error: 除数不能为0
--- 执行完毕 ---
四、主动抛出异常:raise
有时候,并不是代码出错了,而是业务逻辑不满足,我们需要主动报错,通知调用者。这时使用 raise 关键字。
def set_age(age):
if age < 0 or age > 150:
# 主动抛出一个值错误
raise ValueError("年龄必须在 0 到 150 之间")
print(f"年龄设置为: {age}")
try:
set_age(200)
except ValueError as e:
print(f"设置失败: {e}")
五、自定义异常
虽然 Python 内置了很多异常,但在大型项目中,为了更清晰地表达业务错误,我们通常会自定义异常类。只需要继承 Exception 类即可。
# 定义一个"余额不足"异常
class InsufficientFundsError(Exception):
pass
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
# 抛出自定义异常
raise InsufficientFundsError(f"余额不足!当前: {self.balance}, 需要: {amount}")
self.balance -= amount
print(f"取款成功,剩余: {self.balance}")
account = BankAccount(100)
try:
account.withdraw(200)
except InsufficientFundsError as e:
print(f"交易拒绝: {e}")
六、实战练习
练习1:健壮的整数输入器
编写一个函数 get_integer(prompt),循环提示用户输入内容,直到用户输入一个合法的整数为止。
def get_integer(prompt):
while True:
user_input = input(prompt)
try:
value = int(user_input)
return value # 成功转换,返回结果并退出循环
except ValueError:
print(f"输入无效:'{user_input}' 不是一个整数,请重试。")
# 测试
age = get_integer("请输入你的年龄: ")
print(f"你的年龄是: {age}")
练习2:安全的文件读取
编写一个程序读取文件,如果文件不存在,提示用户;如果文件编码错误,也提示用户;无论如何最后都要打印"操作结束"。
def safe_read(filename):
try:
with open(filename, "r", encoding="utf-8") as f:
print(f.read())
except FileNotFoundError:
print(f"错误: 文件 '{filename}' 未找到。")
except UnicodeDecodeError:
print(f"错误: 文件 '{filename}' 编码格式不对,无法读取。")
except Exception as e:
print(f"发生未知错误: {e}")
finally:
print("--- 读取操作结束 ---")
# 测试
safe_read("not_exist.txt")
七、常见问题
Q1:try 块里应该放多少代码?
越少越好。只把可能报错的那几行关键代码放进去。如果在 try 里放了几百行代码,一旦报错,你很难定位到底是哪一行出了问题。
Q2:except 可以不写类型吗?
可以写 except:,但这等同于捕获所有异常(包括 Ctrl+C 中断)。这是一种极坏的编程习惯,强烈建议至少写 except Exception:。
Q3:return 和 finally 谁先执行?
如果 try 块中有 return,finally 会在 return 之前执行。千万不要在 finally 里面写 return,否则会覆盖掉原本的返回值。
八、小结
关键要点:
- 预判错误:用
try...except包裹可能出错的代码。 - 精准捕获:尽量指定具体的异常类型(如
ValueError),避免使用笼统的Exception。 - 善后处理:资源释放(如关闭文件)一定要放在
finally中。 - 业务逻辑:不满足条件时,可以用
raise主动中断程序。
九、课后作业
- 简易除法器:编写程序接收两个用户输入的数字进行除法运算。需要处理:输入非数字、除数为0、以及未知错误。
- 列表越界保护:编写一个函数
safe_get(lst, index),接收列表和索引。如果索引有效,返回对应的元素;如果索引越界,捕获IndexError并返回None(而不是报错)。 - 用户注册系统:定义一个
register(username, password)函数。- 如果用户名长度小于3,抛出自定义异常
UsernameTooShortError。 - 如果密码长度小于6,抛出自定义异常
PasswordTooShortError。 - 编写调用代码,捕获并打印这些错误。
- 如果用户名长度小于3,抛出自定义异常
下节预告
Day 18:常用内置模块 (JSON, Datetime, Random) - 之前简单提过标准库,明天我们将深入讲解开发中最离不开的几个模块,学会处理时间、数据交换和随机事件。
系列导航:
- 上一篇:Day 16 - 面向对象编程(下)
- 下一篇:Day 18 - 常用内置模块(待更新)
更多推荐


所有评论(0)