news 2026/5/15 6:22:36

Day 17:【99天精通Python】异常处理 - 让程序稳如泰山

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day 17:【99天精通Python】异常处理 - 让程序稳如泰山

Day 17:【99天精通Python】异常处理 - 让程序稳如泰山

前言

欢迎来到第17天!

在编程的世界里,不出 bug 是不可能的。即便是最顶尖的程序员,也无法保证代码永远不出错。用户可能会输入非法数据,文件可能突然被删除,网络可能会断开。

如果程序一遇到错误就直接崩溃(闪退),用户体验会非常差。异常处理 (Exception Handling)就是给程序穿上一层"防弹衣",当意外发生时,我们能优雅地捕获错误,并给出友好的提示,而不是让程序直接挂掉。

本节内容:

  • 什么是异常?
  • try...except基本结构
  • 捕获多种异常
  • elsefinally的作用
  • 主动抛出异常raise
  • 自定义异常类
  • 实战练习

一、什么是异常 (Exception)?

异常是指程序在运行过程中发生的错误,它会打断指令的正常执行。

常见的异常类型

  • SyntaxError: 语法错误(通常在运行前就会报错)。
  • NameError: 尝试访问一个未定义的变量。
  • TypeError: 类型不匹配(例如数字 + 字符串)。
  • ZeroDivisionError: 除数为 0。
  • IndexError: 列表索引越界。
  • KeyError: 字典中不存在该键。
  • FileNotFoundError: 打开不存在的文件。

二、基本语法:try…except

我们使用try块包含可能出错的代码,用except块来处理错误。

2.1 基本结构

try:# 可能会报错的代码num=int(input("请输入一个数字: "))result=10/numprint(f"计算结果:{result}")exceptZeroDivisionError:# 当发生除以0错误时执行print("错误:除数不能为0!")exceptValueError:# 当输入的不是数字时执行print("错误:请输入有效的整数!")

2.2 获取异常信息

有时我们需要知道具体的报错信息,可以使用as关键字。

try:print(1/0)exceptZeroDivisionErrorase:print(f"捕获到错误:{e}")# 捕获到错误: division by zero

2.3 捕获所有异常 (慎用)

可以使用Exception捕获所有类型的运行时错误。但通常不推荐这样做,因为这会掩盖一些意想不到的 bug(比如变量名写错)。

try:# 复杂逻辑passexceptExceptionase:print(f"发生未知错误:{e}")

三、完善结构:else 与 finally

异常处理还有两个可选的子句:elsefinally

  • else: 当try没有发生异常时执行。
  • finally:无论是否发生异常,最终都会执行(常用于清理资源,如关闭文件、断开数据库)。
defdivision_test(x,y):try:result=x/yexceptZeroDivisionError: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关键字。

defset_age(age):ifage<0orage>150:# 主动抛出一个值错误raiseValueError("年龄必须在 0 到 150 之间")print(f"年龄设置为:{age}")try:set_age(200)exceptValueErrorase:print(f"设置失败:{e}")

五、自定义异常

虽然 Python 内置了很多异常,但在大型项目中,为了更清晰地表达业务错误,我们通常会自定义异常类。只需要继承Exception类即可。

# 定义一个"余额不足"异常classInsufficientFundsError(Exception):passclassBankAccount:def__init__(self,balance):self.balance=balancedefwithdraw(self,amount):ifamount>self.balance:# 抛出自定义异常raiseInsufficientFundsError(f"余额不足!当前:{self.balance}, 需要:{amount}")self.balance-=amountprint(f"取款成功,剩余:{self.balance}")account=BankAccount(100)try:account.withdraw(200)exceptInsufficientFundsErrorase:print(f"交易拒绝:{e}")

六、实战练习

练习1:健壮的整数输入器

编写一个函数get_integer(prompt),循环提示用户输入内容,直到用户输入一个合法的整数为止。

defget_integer(prompt):whileTrue:user_input=input(prompt)try:value=int(user_input)returnvalue# 成功转换,返回结果并退出循环exceptValueError:print(f"输入无效:'{user_input}' 不是一个整数,请重试。")# 测试age=get_integer("请输入你的年龄: ")print(f"你的年龄是:{age}")

练习2:安全的文件读取

编写一个程序读取文件,如果文件不存在,提示用户;如果文件编码错误,也提示用户;无论如何最后都要打印"操作结束"。

defsafe_read(filename):try:withopen(filename,"r",encoding="utf-8")asf:print(f.read())exceptFileNotFoundError:print(f"错误: 文件 '{filename}' 未找到。")exceptUnicodeDecodeError:print(f"错误: 文件 '{filename}' 编码格式不对,无法读取。")exceptExceptionase:print(f"发生未知错误:{e}")finally:print("--- 读取操作结束 ---")# 测试safe_read("not_exist.txt")

七、常见问题

Q1:try块里应该放多少代码?

越少越好。只把可能报错的那几行关键代码放进去。如果在try里放了几百行代码,一旦报错,你很难定位到底是哪一行出了问题。

Q2:except可以不写类型吗?

可以写except:,但这等同于捕获所有异常(包括Ctrl+C中断)。这是一种极坏的编程习惯,强烈建议至少写except Exception:

Q3:returnfinally谁先执行?

如果try块中有returnfinally会在return之前执行。千万不要在finally里面写return,否则会覆盖掉原本的返回值。


八、小结

无错误

发生异常

异常处理流程

try: 执行代码

执行 else (可选)

匹配 except 类型?

执行 except 块 (处理错误)

向上抛出异常 (程序崩溃)

执行 finally (无论如何都执行)

其他核心

raise: 主动报错

自定义异常: 继承 Exception

关键要点

  1. 预判错误:用try...except包裹可能出错的代码。
  2. 精准捕获:尽量指定具体的异常类型(如ValueError),避免使用笼统的Exception
  3. 善后处理:资源释放(如关闭文件)一定要放在finally中。
  4. 业务逻辑:不满足条件时,可以用raise主动中断程序。

九、课后作业

  1. 简易除法器:编写程序接收两个用户输入的数字进行除法运算。需要处理:输入非数字、除数为0、以及未知错误。
  2. 列表越界保护:编写一个函数safe_get(lst, index),接收列表和索引。如果索引有效,返回对应的元素;如果索引越界,捕获IndexError并返回None(而不是报错)。
  3. 用户注册系统:定义一个register(username, password)函数。
    • 如果用户名长度小于3,抛出自定义异常UsernameTooShortError
    • 如果密码长度小于6,抛出自定义异常PasswordTooShortError
    • 编写调用代码,捕获并打印这些错误。

下节预告

Day 18:常用内置模块 (JSON, Datetime, Random)- 之前简单提过标准库,明天我们将深入讲解开发中最离不开的几个模块,学会处理时间、数据交换和随机事件。


系列导航

  • 上一篇:Day 16 - 面向对象编程(下)
  • 下一篇:Day 18 - 常用内置模块(待更新)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/11 4:09:26

Keil uVision5使用教程:一文说清RTOS在工控中的集成方法

从零开始掌握 Keil uVision5 中的 RTOS 集成&#xff1a;工控开发实战指南你有没有遇到过这样的场景&#xff1f;一个简单的温控系统&#xff0c;既要定时采集传感器数据&#xff0c;又要刷新显示屏&#xff0c;还得响应按键操作和串口指令。用传统的“主循环轮询”方式写代码&…

作者头像 李华
网站建设 2026/5/1 6:55:14

Altium Designer在温度控制系统中的项目应用

从原理到量产&#xff1a;用 Altium Designer 打造高精度温度控制系统 在工业自动化、医疗设备和精密仪器领域&#xff0c;一个稳定可靠的温度控制系统&#xff0c;往往决定了整台设备的性能上限。无论是恒温培养箱、半导体工艺加热平台&#xff0c;还是高端家电中的智能温控模…

作者头像 李华
网站建设 2026/5/11 1:17:50

桥式整流电路设计要点:整流二极管实战案例

从一颗二极管说起&#xff1a;桥式整流电路的实战设计陷阱与避坑指南你有没有遇到过这样的情况——电源板莫名其妙“冒烟”&#xff0c;拆开一看&#xff0c;桥堆炸了&#xff1f;或者设备在高温环境下频繁重启&#xff0c;排查半天发现是整流环节出了问题&#xff1f;别急&…

作者头像 李华
网站建设 2026/5/8 21:01:58

图解说明usb_burning_tool固件定制中的关键参数设置

深入剖析usb_burning_tool刷机工具&#xff1a;从参数配置到量产落地的实战指南 你有没有遇到过这样的场景&#xff1f; 产线上的TV Box批量烧录&#xff0c;几十台设备同时连接PC&#xff0c;结果一半“脱机”&#xff0c;三分之一写入失败&#xff0c;还有几台直接变砖……排…

作者头像 李华
网站建设 2026/5/13 23:07:33

快速理解继电器驱动电路设计关键步骤

从零搞懂继电器驱动电路&#xff1a;工程师避坑实战指南你有没有遇到过这种情况——明明代码写得没问题&#xff0c;MCU也正常输出高电平&#xff0c;可继电器就是“抽风”&#xff1a;时而吸合、时而不吸&#xff1b;更糟的是&#xff0c;某天突然烧了单片机IO口&#xff0c;甚…

作者头像 李华
网站建设 2026/5/11 9:46:15

ARM内存管理基础:入门级全面讲解

深入ARM内存管理&#xff1a;从零理解MMU与页表机制你有没有遇到过这样的问题——在调试一段裸机代码时&#xff0c;程序一开启MMU就崩溃&#xff1f;或者在移植操作系统时&#xff0c;发现某个外设寄存器读写异常&#xff0c;查了半天才发现是内存属性配置错了&#xff1f;这些…

作者头像 李华