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 系列:使用 raise 主动引发异常,让程序学会检查数据

上一篇我们认识了 Python 的常见异常类型,学习了 ValueError、TypeError 等异常的区别,也了解了如何使用 try 和 except 捕获异常。

不过,异常通常有两种来源。

一种是 Python 在执行代码时发现了问题。例如,使用 int("abc") 将无法转换的字符串转成整数,就会产生 ValueError。

另一种是程序本身有明确的业务规则。例如,年龄不能为负数。虽然 -5 是合法的整数,但它不符合我们的业务要求。

这时,我们就需要使用 raise 主动引发异常。

这篇文章就从一个简单的年龄检查程序开始,学习如何使用 raise 检查数据、主动引发异常,以及配合 try 和 except 完整处理输入错误。

为什么需要主动引发异常

先看一个简单的例子。

假设我们希望编写一个程序,让用户输入年龄,并输出输入的结果。

创建 raise_demo.py,写入下面的代码:

1
2
3
age = int(input("请输入年龄:"))

print(f"你的年龄是:{age}")

运行程序:

1
python3 raise_demo.py

如果输入 18,程序会正常输出:

1
2
请输入年龄:18
你的年龄是:18

但如果输入 abc,程序就会报错。

原因是 input() 读取到的内容始终是字符串,而 int() 无法将 "abc" 转换为整数,因此会产生 ValueError。

还有一种情况:如果输入 -5,程序却可以正常输出:

1
2
请输入年龄:-5
你的年龄是:-5

这是因为对于 Python 来说,-5 是合法的整数。int() 只负责转换数据类型,并不知道年龄不能为负数这样的业务规则。

因此,我们需要在程序中主动检查输入数据。

使用 raise 主动引发异常

Python 提供了 raise 语句,用于主动引发异常。

例如:

1
2
3
4
5
6
age = -5

if age < 0:
raise ValueError("年龄不能为负数")

print(f"你的年龄是:{age}")

运行程序后,会产生 ValueError,异常信息为:

1
2
3
Traceback (most recent call last):
...
ValueError: 年龄不能为负数

这段代码可以分成三个部分理解。

  • if age < 0:检查年龄是否小于零。
  • raise ValueError("年龄不能为负数"):如果条件成立,就主动引发 ValueError。
  • print(...):只有前面的代码没有引发异常,程序才会继续执行到这里。

这里的 raise 可以理解为程序主动发出的一个信号:当前数据不符合要求,需要停止正常流程,并交由异常处理机制处理。

需要注意,raise 并不是用来修复错误的。它负责指出问题,具体如何处理,则取决于程序是否编写了相应的异常处理代码。

补全异常捕获,让程序能够处理输入错误

前面的例子还有一个问题。

如果用户输入 abc,程序会因为 int() 转换失败而报错;如果用户输入 -5,程序又会因为我们主动引发的异常而报错。

在实际应用中,我们通常不希望程序直接把错误信息和调用栈显示给普通用户,而是希望给出清晰的提示,让用户有机会重新输入。

这就需要把 raise 与之前学习的 try 和 except 结合起来。

将 raise_demo.py 修改为下面的完整代码:

1
2
3
4
5
6
7
8
9
10
11
try:
age = int(input("请输入年龄:"))

if age < 0:
raise ValueError("年龄不能为负数")

except ValueError as e:
print(f"输入错误:{e}")

else:
print(f"你的年龄是:{age}")

运行程序:

1
python3 raise_demo.py

我们分别测试几种不同的输入。

输入正常的年龄

输入 18:

1
2
请输入年龄:18
你的年龄是:18

程序的执行过程如下:

  1. input() 读取输入内容 "18"。
  2. int() 将字符串转换为整数 18。
  3. age < 0 的判断结果为 False,不会执行 raise。
  4. try 代码块正常执行结束,程序进入 else,输出年龄。

输入非数字内容

输入 abc:

1
2
请输入年龄:abc
输入错误:invalid literal for int() with base 10: 'abc'

这次,程序在执行 int(input(...)) 时就产生了 ValueError。

因为异常发生在 try 代码块内部,Python 会跳过该代码块中剩余的语句,查找匹配的 except。

这里的 except ValueError as e 捕获了异常,并将异常对象赋值给变量 e。通过 print(f"输入错误:{e}"),程序就可以显示异常所携带的信息。

输入负数

输入 -5:

1
2
请输入年龄:-5
输入错误:年龄不能为负数

这次,int() 转换成功,程序继续执行年龄检查。

由于 age < 0 的判断结果为 True,程序执行:

1
raise ValueError("年龄不能为负数")

主动引发 ValueError。

随后,程序进入同一个 except ValueError as e 分支,输出我们自定义的错误信息。

这就说明,自动产生的异常和使用 raise 主动引发的异常,都可以由匹配的 except 捕获。

不过,这里也有一个值得注意的地方:非数字输入和负数输入虽然原因不同,但在这个示例中都使用了 ValueError。因此,捕获后需要根据实际需求决定如何向用户解释问题。

如果希望给两种错误显示不同的提示,可以将输入转换和业务检查分别处理。

让不同的错误显示不同的提示

在刚才的例子中,输入 abc 时,Python 返回的是比较技术化的错误信息:

1
invalid literal for int() with base 10: 'abc'

对于刚接触程序的用户来说,这样的信息不够直观。

我们可以把数据转换和业务检查分开,分别给出更清晰的提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try:
age = int(input("请输入年龄:"))

except ValueError:
print("输入格式错误,请输入整数!")

else:
try:
if age < 0:
raise ValueError("年龄不能为负数")

except ValueError as e:
print(f"输入错误:{e}")

else:
print(f"你的年龄是:{age}")

运行后,输入不同内容会得到不同的结果。

输入 abc:

1
2
请输入年龄:abc
输入格式错误,请输入整数!

输入 -5:

1
2
请输入年龄:-5
输入错误:年龄不能为负数

输入 18:

1
2
请输入年龄:18
你的年龄是:18

这里使用了两个 try 代码块。

第一个负责将输入转换为整数。如果转换失败,就输出格式错误提示。

只有转换成功后,程序才会进入外层的 else,继续检查年龄是否符合业务规则。第二个 try 用于捕获负数检查过程中主动引发的 ValueError。

这种写法能清楚地区分两类问题,但代码也更长了。对于简单的入门示例,前面使用一个 try 和一个 except 的版本通常更容易理解;如果业务逻辑越来越复杂,再根据实际需要拆分处理过程。

raise 和 except 是如何配合工作的

现在,我们已经知道如何使用 raise 主动引发异常,也知道如何使用 except 捕获异常。

可以把两者的关系理解为:

  • raise:发现不符合要求的情况后,主动引发异常。
  • except:发现有匹配的异常后,执行相应的处理逻辑。

它们可以配合起来,让程序在发现问题时及时停止当前的正常执行流程,并转入异常处理流程。

下面用流程图梳理完整过程。

flowchart TD
    A["开始执行程序"] --> B["读取用户输入"]
    B --> C["使用 int() 转换为整数"]
    C --> D{"转换是否成功?"}

    D -->|否| E["自动产生 ValueError"]
    D -->|是| F{"年龄是否小于 0?"}

    F -->|是| G["raise 主动引发 ValueError"]
    F -->|否| H["进入 else,输出年龄"]

    E --> I["except 捕获 ValueError"]
    G --> I

    I --> J["输出错误提示"]
    H --> K["程序正常结束"]
    J --> K

    classDef normal fill:#e3f2fd,stroke:#1976d2,color:#0d47a1;
    classDef decision fill:#fff3e0,stroke:#ef6c00,color:#e65100;
    classDef error fill:#ffebee,stroke:#c62828,color:#b71c1c;
    classDef endNode fill:#e8f5e9,stroke:#388e3c,color:#1b5e20;

    class A,B,C,H normal;
    class D,F decision;
    class E,G,I,J error;
    class K endNode;

这张图里有两条需要重点理解的路径:

  • 转换失败:输入非数字内容,int() 自动产生 ValueError,程序进入 except。
  • 业务检查失败:整数转换成功,但年龄小于零,程序通过 raise 主动引发 ValueError,随后同样进入 except。

如果输入合法整数,程序就不会引发异常,而是进入 else,输出正常结果。

raise 什么时候适合使用

raise 常用于程序已经发现某个条件不符合要求,需要明确报告问题的场景。

例如:

  • 年龄不能为负数。
  • 端口号必须处于有效范围。
  • 配置文件中必须包含某个必填参数。
  • 调用某个业务功能时,传入的参数不符合要求。

不过,不是所有不合法输入都必须通过 raise 处理。

例如,用户输入非数字内容时,程序可以捕获 ValueError,提示用户重新输入。如果业务代码发现一个不可能成立的条件,也可以主动引发异常,避免程序继续使用错误的数据。

关键在于:先明确什么情况属于错误,再决定应该使用哪种异常,以及由哪一层代码负责处理。

写在最后

通过这篇文章,我们学习了如何使用 raise 主动引发异常,并将它与 try、except 和 else 结合起来,完成一个可以处理输入错误的年龄检查程序。

现在,你应该能够区分两种常见情况:

  • Python 在执行操作时自动产生异常,例如将 "abc" 转换为整数时产生 ValueError。
  • 程序根据业务规则主动引发异常,例如发现年龄为负数时使用 raise ValueError(...)。

同时,我们还补全了异常捕获流程,分别处理非数字输入、负数输入和正常输入。

下一篇,我们将学习如何定义自己的异常类型,让程序能够使用更贴近实际业务含义的异常来描述问题。