1
2
3
4
5
6
7
作者:李晓辉

联系方式:

1. 微信:Lxh_Chat

2. 邮箱:939958092@qq.com
  1. Python 系列(一) 从零开始认识 Python,入门其实没那么难
  2. Python 系列(二) 学 Python 的第一步,把开发环境搭建好
  3. Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
  4. Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
  5. Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
  6. Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
  7. Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
  8. Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
  9. Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
  10. Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
  11. Python 系列(十一) list、tuple、set、dict,到底该怎么选?
  12. Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
  13. Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
  14. Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
  15. Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
  16. Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
  17. Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
  18. Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
  19. Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
  20. Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
  21. Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
  22. Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
  23. Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
  24. Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
  25. Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
  26. Python 系列(二十六) Python 模块入门,从 import 到模块复用
  27. Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
  28. Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
  29. Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
  30. Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
  31. Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
  32. Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
  33. Python 系列(三十三) 什么是类和对象?零基础理解 Python class
  34. Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
  35. Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
  36. Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
  37. Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
  38. Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
  39. Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
  40. Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
  41. Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
  42. Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常

上一篇我们认识了 Python 中的异常,知道了程序运行时可能遇到各种问题。例如,用户输入的内容不是数字,程序尝试将它转换为整数时,就可能出现 ValueError 异常。

那么,问题来了:如果程序遇到了异常,能不能不要直接报错退出,而是给出友好的提示,让程序有机会继续执行后续任务?

当然可以。Python 提供了 try 和 except,帮助我们捕获并处理异常。

这篇文章将从一个简单的 CPU 使用率检查程序开始,逐步学习 try、except、else 和 finally,并进一步实现实际的输入范围校验。

从一个实际问题开始

假设我们正在编写一个简单的服务器监控工具,需要用户输入 CPU 使用率,再根据输入的数值判断服务器是否存在性能风险。

先创建一个名为 cpu_check.py 的文件。

如果你使用 Linux,可以执行下面的命令创建文件:

1
touch cpu_check.py

然后使用自己熟悉的编辑器打开这个文件,例如:

1
vim cpu_check.py

写入下面的代码:

1
2
3
cpu_usage = int(input("请输入 CPU 使用率:"))

print(f"当前 CPU 使用率为:{cpu_usage}%")

保存文件后,在文件所在目录运行:

1
python3 cpu_check.py

如果输入 75,程序会输出:

1
2
请输入 CPU 使用率:75
当前 CPU 使用率为:75%

看起来一切正常。

但是,如果输入的不是数字,而是 abc 呢?

1
请输入 CPU 使用率:abc

程序就会报错。因为 input() 接收到的内容是字符串,而 int() 无法将 abc 转换为整数,所以 Python 会产生 ValueError 异常。

这里还存在另一个问题。

如果用户输入 999,程序虽然不会因为整数转换而报错,却会把 CPU 使用率显示为 999%。

这显然不符合我们的预期。对于通常意义上的 CPU 使用率百分比,合理的输入范围应该是 0 到 100。

因此,我们需要解决两个不同的问题:

  • 输入格式检查: 用户输入的内容能不能转换为整数?
  • 输入范围检查: 转换成功后,这个整数是否在合理范围内?

第一个问题可以通过异常处理来解决,第二个问题则需要通过条件判断来完成。

使用 try 和 except 捕获异常

先解决输入格式的问题。

修改 cpu_check.py 中的代码:

1
2
3
4
5
try:
cpu_usage = int(input("请输入 CPU 使用率:"))
print(f"当前 CPU 使用率为:{cpu_usage}%")
except ValueError:
print("输入错误,请输入一个整数!")

保存后,仍然使用同一条命令运行:

1
python3 cpu_check.py

当输入 75 时,输出如下:

1
2
请输入 CPU 使用率:75
当前 CPU 使用率为:75%

当输入 abc 时,输出如下:

1
2
请输入 CPU 使用率:abc
输入错误,请输入一个整数!

这次,程序没有直接显示异常追踪信息,而是执行了我们提前编写的错误处理代码。

这是怎么做到的?

try:尝试执行可能发生异常的代码

try 可以理解为:先尝试执行这里的代码,如果发生异常,就由后面的异常处理逻辑决定如何应对。

例如:

1
2
3
try:
cpu_usage = int(input("请输入 CPU 使用率:"))
print(f"当前 CPU 使用率为:{cpu_usage}%")

这里有两个操作:

  • input() 接收用户输入。
  • int() 尝试将输入内容转换为整数。

如果用户输入 75,转换成功,程序继续执行 try 中的下一条语句。

如果用户输入 abc,转换就会产生 ValueError 异常。

发生异常后,Python 不会继续执行当前 try 代码块中尚未执行的语句,而是开始查找能够处理这个异常的 except 子句。

except:处理指定类型的异常

接下来是:

1
2
except ValueError:
print("输入错误,请输入一个整数!")

这里的 except ValueError 表示:如果前面的 try 代码发生了 ValueError 异常,就执行下面缩进的代码。

我们可以这样理解:

  • try:尝试执行代码。
  • except ValueError:如果发生匹配的 ValueError 异常,就执行指定的处理代码。

需要注意,except ValueError 并不是处理所有异常的万能语句。它主要用于捕获 ValueError 及其子类异常。

例如,如果程序因为变量名称写错而产生 NameError,这个 except ValueError 就不会处理它。没有其他匹配的异常处理逻辑时,该异常仍然会向外传播。

因此,编写异常处理代码时,应该根据实际可能出现的问题,选择合适的异常类型,而不是简单地把所有异常都忽略掉。

理解异常发生后的执行顺序

异常处理最重要的地方,不只是记住语法,还要理解程序的执行顺序发生了什么变化。

继续修改 cpu_check.py:

1
2
3
4
5
6
7
8
9
10
try:
print("开始检查服务器")

cpu_usage = int("abc")

print("CPU 使用率检查完成")
except ValueError:
print("CPU 使用率数据格式错误")

print("继续执行后续任务")

运行:

1
python3 cpu_check.py

输出结果:

1
2
3
开始检查服务器
CPU 使用率数据格式错误
继续执行后续任务

为什么没有输出 CPU 使用率检查完成?

因为程序执行到 int("abc") 时产生了 ValueError 异常。Python 随即跳过当前 try 代码块中剩余的语句,转而执行能够匹配该异常的 except 分支。

异常处理完成后,程序继续执行整个异常处理结构后面的代码,因此最后的 print() 仍然能够执行。

执行过程可以概括为:

flowchart TD
    A["进入 try 代码块"] --> B["执行代码"]
    B --> C{"是否发生异常?"}
    C -->|没有| D["继续执行 try 中的代码"]
    D --> E["跳过 except"]
    C -->|发生匹配的异常| F["跳过 try 中剩余的代码"]
    F --> G["执行对应的 except"]
    E --> H["继续执行异常处理结构后面的代码"]
    G --> H

需要注意,异常被捕获并处理,并不意味着程序中所有问题都已经解决。异常处理能否让程序继续工作,取决于我们编写的处理逻辑。

使用 else 处理正常执行的情况

有时候,我们希望把正常情况下要执行的代码,与异常处理代码分开。

Python 提供了 else 子句。

else 只有在 try 代码块没有发生异常时才会执行。

修改 cpu_check.py:

1
2
3
4
5
6
try:
cpu_usage = int(input("请输入 CPU 使用率:"))
except ValueError:
print("输入错误,请输入一个整数!")
else:
print(f"整数转换成功,得到的值是:{cpu_usage}")

运行:

1
python3 cpu_check.py

输入 75:

1
2
请输入 CPU 使用率:75
整数转换成功,得到的值是:75

输入 abc:

1
2
请输入 CPU 使用率:abc
输入错误,请输入一个整数!

可以看到:

  • 输入 75,整数转换成功,没有发生异常,因此执行 else。
  • 输入 abc,产生 ValueError,执行 except,跳过 else。

这里有一个容易误解的地方:else 判断的是 try 代码块是否正常结束,而不是判断用户输入的内容是否符合所有业务要求。

例如,输入 999 时,int("999") 可以正常执行,所以 else 仍然会执行。

这就是为什么我们还需要额外进行输入范围校验。

另外,else 是可选的。如果没有需要单独放在正常分支中的代码,就不必使用它。

使用 finally 执行收尾工作

在实际运维工作中,程序可能需要打开文件、连接数据库或者访问远程服务器。

这些操作可能涉及需要在使用完毕后释放的资源。即使执行过程中发生异常,我们通常也希望有机会完成必要的收尾工作。

Python 提供了 finally 子句。

finally 中的代码通常无论 try 是否发生异常都会执行。

修改 cpu_check.py:

1
2
3
4
5
6
7
8
try:
cpu_usage = int(input("请输入 CPU 使用率:"))
except ValueError:
print("输入错误,请输入一个整数!")
else:
print(f"整数转换成功,得到的值是:{cpu_usage}")
finally:
print("本次检查流程结束")

运行:

1
python3 cpu_check.py

输入正确的数字:

1
2
3
请输入 CPU 使用率:75
整数转换成功,得到的值是:75
本次检查流程结束

输入错误的内容:

1
2
3
请输入 CPU 使用率:abc
输入错误,请输入一个整数!
本次检查流程结束

无论输入是否正确,最后都会执行 finally 中的语句。

不过,finally 并不是绝对保证执行的。例如,进程被强制终止或者机器突然断电时,程序可能没有机会执行它。

此外,finally 也不能代替正确的资源管理方式。对于文件等资源,后续还可以学习更适合的 with 语句。

正确理解 try、except、else 和 finally 的结构

现在,我们已经认识了四个关键字。接下来,把它们放在一起理解。

1
2
3
4
5
6
7
8
9
10
11
12
try:
# 尝试执行可能发生异常的代码
pass
except ValueError:
# 处理匹配的 ValueError 异常
pass
else:
# try 没有发生异常时执行
pass
finally:
# 通常用于收尾工作
pass

这里的 pass 是 Python 的占位语句,表示暂时不执行任何操作。它可以帮助我们先搭建代码结构,再逐步补充具体逻辑。

四个部分的作用如下:

关键字作用使用要求
try放置可能发生异常的代码必须
except捕获并处理匹配的异常可以有一个或多个;也可以与 finally 组合使用
else在 try 没有发生异常时执行可选,必须与至少一个 except 配合使用
finally执行通常需要完成的收尾操作可选

需要特别注意,except 和 finally 并不是必须同时出现的。Python 支持 try-except、try-finally、try-except-else 以及 try-except-finally 等合法结构。

但 try 不能单独构成完整的异常处理语句,必须搭配至少一个 except 或一个 finally。

实战:校验 CPU 使用率的输入范围

现在,我们把前面学到的知识真正用起来。

假设要编写一个简单的 CPU 使用率检查程序,需要满足以下要求:

  • 用户必须输入一个整数。
  • CPU 使用率的合理范围是 0 到 100,包括两个边界值。
  • 如果输入的不是整数,提示用户输入格式错误。
  • 如果输入整数但超出范围,提示用户输入范围错误。
  • 如果输入合法,则显示 CPU 使用率,并判断是否需要关注。

这里要区分两类问题:

用户输入是否能转换为整数是否在合理范围内
75是是
abc否无法进行整数范围校验
999是否
-5是否
0是是
100是是

abc 无法转换为整数,因此需要通过 except ValueError 处理。

999 和 -5 都是合法的整数,但不符合 CPU 使用率的业务范围,需要通过 if 条件判断处理。

将 cpu_check.py 的内容替换为下面的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
try:
# 接收用户输入,并尝试转换为整数
cpu_usage = int(input("请输入 CPU 使用率(0-100):"))

except ValueError:
# 输入内容无法转换为整数时执行
print("输入格式错误,请输入一个整数!")

else:
# 只有整数转换成功后,才会执行这里
if 0 <= cpu_usage <= 100:
# 检查整数是否处于合法范围
print(f"当前 CPU 使用率为:{cpu_usage}%")

if cpu_usage >= 80:
print("警告:CPU 使用率较高,请关注服务器负载!")
else:
print("CPU 使用率正常。")

else:
# 整数转换成功,但数值超出了合理范围
print("输入范围错误,CPU 使用率必须在 0 到 100 之间!")

finally:
# 无论是否发生异常,通常都会执行
print("本次检查流程结束")

运行命令保持不变:

1
python3 cpu_check.py

测试正常输入

输入 75:

1
2
3
4
请输入 CPU 使用率(0-100):75
当前 CPU 使用率为:75%
CPU 使用率正常。
本次检查流程结束

因为 75 可以转换为整数,并且处于 0 到 100 之间,所以程序正常显示 CPU 使用率。

测试超出范围的输入

输入 999:

1
2
3
请输入 CPU 使用率(0-100):999
输入范围错误,CPU 使用率必须在 0 到 100 之间!
本次检查流程结束

这次没有发生异常,因为 999 本身就是一个整数。

程序通过下面的条件判断发现它超出了合理范围:

1
if 0 <= cpu_usage <= 100:

当条件不成立时,程序进入 else 分支,显示范围错误提示。

测试无法转换为整数的输入

输入 abc:

1
2
3
请输入 CPU 使用率(0-100):abc
输入格式错误,请输入一个整数!
本次检查流程结束

此时,int() 转换失败,产生 ValueError,程序进入 except 分支。

由于异常发生在 try 中,后面的 else 不会执行,但 finally 中的收尾语句仍会执行。

理解这段程序的执行顺序

flowchart TD
    A["接收用户输入"] --> B["int() 尝试转换为整数"]
    B --> C{"转换是否成功?"}
    C -->|否| D["except:提示输入格式错误"]
    C -->|是| E["进入 else,检查数值范围"]
    E --> F{"是否在 0 到 100 之间?"}
    F -->|否| G["提示输入范围错误"]
    F -->|是| H["显示 CPU 使用率"]
    H --> I{"CPU 使用率是否达到 80%?"}
    I -->|是| J["提示 CPU 使用率较高"]
    I -->|否| K["提示 CPU 使用率正常"]
    D --> L["finally:输出流程结束"]
    G --> L
    J --> L
    K --> L

通过这个例子,我们可以看到,异常处理和条件判断解决的是不同的问题。

异常处理负责应对执行过程中出现的异常,例如无法将字符串转换为整数;条件判断负责检查业务规则,例如 CPU 使用率是否处于合理范围。

两者结合,才能让程序更加健壮。

需要注意,这里使用的 80% 只是演示用的告警阈值,并不是适用于所有服务器和业务的统一标准。

写在最后

通过这篇文章,我们学习了 Python 最基础的异常处理结构:

  • try:尝试执行可能发生异常的代码。
  • except:捕获并处理匹配的异常。
  • else:在 try 没有发生异常时执行。
  • finally:执行通常需要完成的收尾工作。

我们还通过 CPU 使用率检查的例子,区分了输入格式校验和输入范围校验。

这两个概念在实际编程中非常重要:能够转换为正确的数据类型,不代表输入的数据就符合业务要求。

异常处理也不意味着把所有错误都隐藏起来。合理的做法是识别可能出现的问题,选择合适的异常类型,并给出有帮助的提示。对于没有预料到的程序错误,不应该随意忽略。

下一篇,我们将进一步学习 Python 的异常继承关系,理解为什么 ValueError、TypeError 等异常属于不同的类型,以及 Python 为什么允许我们按照异常类型进行捕获。