1. 异常处理为什么重要?
鲁迅曾经说过:我想不写异常处理,或许是跟自己过不去罢!
相信大家肯定遇到这种情况:你写了一个程序,看起来很完美,能接受用户输入,还能做一些复杂计算。
你满怀期待地运行,突然——砰!崩溃了!
比如我想模拟一个ATM取款系统,代码如下:
def withdraw(balance, amount):
return balance - amount
def main():
balance = float(input("请输入账户余额:"))
amount = float(input("请输入取款金额:"))
new_balance = withdraw(balance, amount)
print(f"取款成功!剩余余额:{new_balance}")
main()
看起来很简单吧?一个函数计算余额,一个输入输出逻辑,没毛病!那我们试试看运行。
**测试一:**如果输入 100
和 50
:
取款成功!剩余余额:50.0
太棒了,功能完美!
测试二:如果手滑,输入了 qwe
:
ValueError: could not convert string to float
程序崩溃,结束了。
**测试三:**如果余额是 100
,取款金额是 200
:
取款成功!剩余余额:-100.0
等等,这是什么鬼?负余额?ATM还倒贴钱?
问题来了:这样的代码能用吗?
- 用户体验在哪里? 一输入错,没有任何提示,程序直接死掉了!
- 程序逻辑正确吗? 明明没钱了,系统居然还能取款,难道不应该提醒用户余额不足吗?
- 扩展性如何? 如果以后有更多功能,比如转账或支付,光靠这些代码,能扛住吗?
所以很多时候我们的代码看起来能正常运行,其实是建立在你了解代码的内部细节的基础上,真让用户去使用的话,天知道会出现什么问题!
因此,健壮性💪🏻是我们衡量程序好坏的一个重要指标。
👉🏻****健壮性指即使面对用户的错误操作或不可预知的异常情况,程序依然可以稳定运行,并提供清晰的反馈。
一个健壮的程序需要:
- 能处理各种异常情况,而不是直接退出。
- 提供清晰的错误提示,帮助用户理解问题。
- 确保系统逻辑完整性,避免出现负余额、超范围等异常状态。
2. 如何进行异常处理?
那么这个时候,我们就需要开始“叠甲”了,给代码套上面对各种问题的护甲。
所谓异常处理,就是一种程序设计中的重要机制,用于在程序执行过程中处理可能出现的异常情况或错误,如文件不存在、网络连接失败、用户输入不合法、除以零、内存不足等,让代码能够从容应对,甚至优雅地告诉用户“哪里出了问题”。
Python 提供了强大的异常处理工具,主要保留字如下:
try
:尝试执行一段代码。except
:如果出错了,执行备用方案。else
:如果没有异常,执行这部分代码。finally
:无论有没有异常,最后总会执行。
2.1. try
和 except
基本的异常处理结构如下:
try:
# 尝试运行某段代码
except:
# 如果发生异常,执行这段代码
我们将其应用到刚刚的例子中,先解决一个最常见的问题——用户输入非法数据。
加入try-except
改进后代码:
def withdraw(balance, amount):
return balance - amount
def main():
try:
balance = float(input("请输入账户余额:"))
amount = float(input("请输入取款金额:"))
new_balance = withdraw(balance, amount)
print(f"取款成功!剩余余额:{new_balance}")
except ValueError:
print("请输入有效的数字!")
main()
运行一下:输入 abc
时,程序不再崩溃,而是向用户输出
请输入有效的数字!
2.2. else
和 finally
在异常处理结构中,else
和 finally
提供了额外的控制:
**else**
:当没有发生任何异常时执行。**finally**
:无论是否发生异常,总会执行的代码块(常用于资源释放)。
我们也来应用试试!
def main():
try:
balance = float(input("请输入账户余额:"))
amount = float(input("请输入取款金额:"))
new_balance = withdraw(balance, amount)
except ValueError as e:
print("请输入有效的数字!")
else:
print(f"取款成功!剩余余额:{new_balance}")
finally:
print("交易结束,谢谢使用!")
main()
在这段代码中,无论是否发生异常,都会输出"交易结束,谢谢使用!"
2.3. 捕获多种异常
在更复杂的场景中,程序可能遇到多种类型的错误。如果我们仅仅使用Exception
则会捕获所有错误,这样太过宽泛,缺乏针对性
except Exception:
# 会把所有错误都捕获,包括无法预测的错误,可能掩盖真正的问题。
我们可以同时捕获不同类型的异常,提供针对性地处理。类似于if
,提供出现某种错误情况后的特定处理方式。
我们给例子添加一个针对余额不足这种情况的处理:
def withdraw(balance, amount):
if amount > balance:
raise ValueError("余额不足!")
return balance - amount
def main():
try:
balance = float(input("请输入账户余额:"))
amount = float(input("请输入取款金额:"))
new_balance = withdraw(balance, amount)
print(f"取款成功!剩余余额:{new_balance}")
except ValueError as e:
print("请输入有效的数字!")
except Exception as e:
print(f"未知错误:{e}")
main()
- 如果余额不足,输出:
错误:余额不足!
- 如果输入非法数据,输出:
"请输入有效的数字!"
2.4. 自定义异常类
直接使用内置异常类型(如 ValueError
)虽然可以解决问题,但在复杂项目中,我们可能需要定义自己的异常,来提供更明确的错误分类。
比如在本例中,我们要区分“余额不足”和“账户冻结”等问题的话,自定义异常类就派上用场了!
增加自定义异常后的代码:
class InsufficientFundsError(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 InsufficientFundsError(balance, amount)
return balance - amount
def main():
try:
balance = float(input("请输入账户余额:"))
amount = float(input("请输入取款金额:"))
new_balance = withdraw(balance, amount)
print(f"取款成功!剩余余额:{new_balance}")
except InsufficientFundsError as e:
print(e)
except ValueError:
print("错误:请输入有效的数字!")
main()
当余额不足时,代码输出: 余额不足!当前余额:100.0,取款金额:200.0
在本例中我们定义了一个InsufficientFundsError
的类,来作为自定义异常类,关于类的具体知识我们将在第三章中学习。
3. 完善细节及注意事项
我们再加入一个循环来与用户交互,就得到了一个健壮、友好且扩展性强的 ATM 取款系统。最终代码如下:
class InsufficientFundsError(Exception):
"""余额不足异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"余额不足!当前余额:{balance:.2f},取款金额:{amount:.2f}")
def deposit(balance, amount):
"""存款功能"""
if amount <= 0:
raise ValueError("存款金额必须为正数!")
return balance + amount
def withdraw(balance, amount):
"""处理取款操作"""
if amount <= 0:
raise ValueError("取款金额必须为正数!")
if amount > balance:
raise InsufficientFundsError(balance, amount)
return balance - amount
def check_balance(balance):
"""查询余额"""
print(f"当前账户余额为:{balance:.2f}")
def main():
print("欢迎使用银行账户系统!")
balance = 1000.0 # 初始账户余额
while True:
print("\n请选择操作:")
print("1. 存款")
print("2. 取款")
print("3. 查询余额")
print("4. 退出")
try:
choice = input("请输入操作编号(1-4):")
if choice == "1":
amount = float(input("请输入存款金额:"))
balance = deposit(balance, amount)
print(f"存款成功!当前余额:{balance:.2f}")
elif choice == "2":
amount = float(input("请输入取款金额:"))
balance = withdraw(balance, amount)
print(f"取款成功!当前余额:{balance:.2f}")
elif choice == "3":
check_balance(balance)
elif choice == "4":
print("感谢使用银行账户系统,再见!")
break
else:
print("无效的操作编号,请重新输入!")
except InsufficientFundsError as e:
print(e)
except ValueError as e:
print(f"输入错误:{e}")
except Exception as e:
print(f"未知错误:{e}")
finally:
print("———操作完成———")
if __name__ == "__main__":
main()
⚠️注意事项:
- 异常处理的范围不宜过大
def process_data(data):
try:
# 多段代码都可能抛出异常
result = int(data[0]) # 可能抛出 IndexError 或 ValueError
print(f"处理后的数据:{result * 2}")
file = open("output.txt", "w")
file.write(f"结果:{result}")
except Exception as e:
print(f"错误:{e}") # 不清楚具体是哪一段代码抛出的异常
像这段代码中,IndexError
、ValueError
和文件操作错误都被混杂在一个 except
中,可读性低。
- 不能在
finally
中修改返回值
def calculate():
try:
return 10 / 2 # 正常返回值是 5.0
finally:
return "执行完成!" # 覆盖了原始返回值
result = calculate()
print(result) # 输出:"执行完成!"
在这个例子中,finally
中的 return
覆盖了 try
中的返回值。即使 try
中的计算逻辑正确,也无法返回正确的结果。
妥善使用异常处理,你就能:
✅ 增强健壮性:让程序即使面对错误也能运行,而不是崩溃。
✅ 提高可维护性:通过自定义异常和清晰的错误处理逻辑,让代码更易扩展。
✅ 提升用户体验:用户遇到错误时能够获得有用的信息,而不是一脸懵逼地看着崩溃提示。
🛠️ 记住:代码不仅要能跑,还要跑得“稳”!