查看全课大纲(第 34 / 51 节)
  1. 1.Python 是什么?为什么值得学?
  2. 2.如何为 Python 编程准备一个“全家桶”环境?
  3. 3.Python 有哪几种执行方法?
  4. 4.行与缩进到底有什么用?
  5. 5.注释有什么用,怎么写?
  6. 6.变量:如何给数据起个好名字?
  7. 7.运算符的优先级和常见坑点是什么?
  8. 8.Python 中,数字和布尔值究竟有哪几种?
  9. 9.输入函数 `input()` 和文件读取 `open()` 怎么用?
  10. 10.如何将程序的结果展示给用户或保存下来?
  11. 11.CSV 文件怎么读怎么写才方便?
  12. 12.列表是什么,如何创建和访问它?
  13. 13.列表的修改操作,有哪些方法?
  14. 14.列表切片为什么能取到部分元素?排序和查找又该怎么用?
  15. 15.判断语句:如何让程序根据条件做不同的事?
  16. 16.for 循环如何遍历序列中的元素?
  17. 17.while 循环什么时候才会停?
  18. 18.while 循环的结束条件,你写对了吗?
  19. 19.循环中途想提前结束,怎么办?
  20. 20.`break` 和 `continue` 到底有什么区别?
  21. 21.列表推导式如何让代码更简洁高效?
  22. 22.字符串怎么定义、拼接和操作?
  23. 23.元组:为什么定义单个元素时要加逗号?
  24. 24.集合(set)怎么用?如何快速去重和做集合运算?
  25. 25.字典是什么,为什么比列表更方便?
  26. 26.字典的增删改查与遍历,如何避免 KeyError?
  27. 27.函数里的变量,为什么有的能用有的不能用?
  28. 28.在函数内部修改全局变量,为什么有时不生效?
  29. 29.函数里能修改全局变量吗?返回值怎么用?
  30. 30.匿名函数:如何用 lambda 快速定义简单函数?
  31. 31.面向对象编程是什么,为什么需要它?
  32. 32.类中的方法和属性到底怎么定义和使用?
  33. 33.如何控制对对象内部属性和方法的访问?
  34. 34.异常发生时,程序会怎样继续执行?
  35. 35.如何组织代码:包和模块
  36. 36.`if __name__ == '__main__'` 到底有什么用?
  37. 37.如何用 IDE 的 Debug 功能排查程序错误?
  38. 38.Matplotlib 绘图的第一步该做什么?
  39. 39.Figure 是什么,如何设置图形尺寸?
  40. 40.如何自定义 Matplotlib 的坐标轴?
  41. 41.如何为图表添加图例,让不同曲线一目了然?
  42. 42.饼图怎么画才能直观展示数据占比?
  43. 43.如何用 Matplotlib 绘制散点图?
  44. 44.柱状图怎么画?
  45. 45.如何使用 Matplotlib 绘制等高线图?
  46. 46.如何在 Matplotlib 中绘制子图?
  47. 47.zip 函数如何把多个列表“打包”在一起?
  48. 48.如何给可迭代对象加上索引?
  49. 49.random 模块如何生成随机数?
  50. 50.math 模块怎么用?
  51. 51.Counter 如何快速统计元素出现次数?

异常发生时,程序会怎样继续执行?

约 19 分钟

小象实战讲义 · Python 编程从入门到实践

在编写程序时,难免会遇到一些意外情况,比如打开一个不存在的文件,或者进行不合法的计算(如除以零)。如果程序直接崩溃,显然不是我们想要的结果。本节学习的异常处理,就是让程序在遇到错误时能够“体面地”应对,而不是直接停止。这是编写健壮程序的关键一环,也是从“基础语法”迈向“实用编程”的重要一步。

💡 核心导读

  • 异常是什么:程序执行时因非法操作或主动触发而抛出的错误信号。
  • 如何捕获异常:使用 try...except 结构,捕获特定或所有类型的异常,并执行相应的处理逻辑。
  • 获取异常信息:通过 as 关键字将异常对象赋值给变量,可以打印或分析具体的错误信息。
  • 确保资源释放:使用 finally 关键字,无论是否发生异常,都会执行其中的代码,常用于关闭文件等资源清理工作。
  • 异常的传递:函数内抛出的异常会一层层向上传递,直到被某个调用者的 try...except 捕获。

异常的产生与抛出

程序运行中,当 Python 解释器遇到无法处理的错误时,就会主动抛出(raise)一个异常。异常一旦抛出,程序会立即中断当前流程,后续代码将不会被执行。

异常的产生主要有两种方式:

  1. 由解释器自动抛出:例如执行非法操作(除以零、访问未定义的变量、打开不存在的文件等)。
  2. 由程序主动抛出:使用 raise 关键字,可以主动触发一个异常。
# 示例一:捕获由非法操作(除以零)引发的异常
try:
    result = 1 / 0  # 这行会引发 ZeroDivisionError
except ZeroDivisionError:
    print("捕获到 ZeroDivisionError: 除数不能为零")

# 示例二:捕获由主动 raise 引发的异常
try:
    print("Hello One")
    raise Exception("这里发生了一个自定义错误")  # 主动抛出异常
    print("Hello Two")  # 这行不会被执行
except Exception as e:
    print("捕获到异常: {}".format(e))
捕获到 ZeroDivisionError: 除数不能为零
Hello One
捕获到异常: 这里发生了一个自定义错误

可以看到,无论是自动还是主动抛出的异常,都会中断 raise 语句之后的代码。Python 内置函数(如 open())在内部遇到错误时,本质上也是通过 raise 将异常抛给我们的。

捕获并处理异常

我们使用 try...except 结构来捕获并处理异常,防止程序直接崩溃。

捕获特定异常,并获取错误信息

将可能出错的代码放在 try: 代码块中。如果其中发生了异常,Python 会尝试匹配 except 后面指定的异常类型;匹配成功,就执行对应的 except 块中的处理代码。使用 except 异常类型 as 变量名,还能把异常对象保存到变量里,打印或记录具体的错误信息。下面用不依赖本地文件的例子演示(比如打开不存在的文件会触发 FileNotFoundError,思路完全相同)。

# 1) 捕获特定异常,并用 as 取出异常对象
try:
    number = int("abc")  # 触发 ValueError
except ValueError as err:
    print("异常类型:", type(err).__name__)
    print("异常信息:", err)

# 2) 一个 except 捕获多种异常类型
try:
    result = 1 / 0
except (ValueError, ZeroDivisionError) as error:
    print("捕获到了异常:", error)

# 3) 用 except Exception 兜底,捕获前面没有处理的其他异常
num = 0  # 模拟用户输入了 0(原程序用 input() 读取)
try:
    result = 1 / num
except ValueError:
    print("输入的不是数字,这是预期内的错误。")
except Exception as err:
    print("发生了未预期的错误: {}".format(err))
异常类型: ValueError
异常信息: invalid literal for int() with base 10: 'abc'
捕获到了异常: division by zero
发生了未预期的错误: division by zero

通常用 except Exception: 兜底处理前面没有捕获到的“意料之外”的错误。

匹配顺序:Python 会从上到下依次检查 except 块,执行第一个匹配到的异常类型对应的代码块。因此,通常将更具体的异常类型放在前面,将 except Exception: 放在最后。

无论如何都要执行的 finally

finally 块中的代码无论是否发生异常,无论异常是否被捕获,最终都会被执行。这使其成为进行资源清理(如关闭文件、释放网络连接)的理想场所。

import io

resource = None  # 预先声明变量
try:
    resource = io.StringIO("异常处理演示")  # 用一个内存资源代替文件,避免依赖本地文件
    print("读取内容:", resource.read())
    raise ValueError("读取过程中出现了问题")  # 模拟处理过程中抛出的异常
except ValueError as error:
    print("捕获到异常: {}".format(error))
finally:
    print("执行 finally 块")
    if resource:  # 检查资源是否被成功创建(不是 None)
        resource.close()
        print("资源已关闭")
读取内容: 异常处理演示
捕获到异常: 读取过程中出现了问题
执行 finally 块
资源已关闭

在上面的例子中,try 块里主动抛出了异常,但 finally 依然会执行,从而保证资源一定被清理。

异常在函数调用中的传递

异常具有传递性。当一个函数内部抛出异常且未被捕获时,这个异常会沿着调用链向上“冒泡”,传递给它的调用者。如果调用者也没有捕获,则继续向上,直到被某个 try...except 捕获,或者传递到程序最顶层导致程序崩溃。

def inner_function():
    print("------inner_function-1------")
    print(undefined_variable)  # 这里会抛出 NameError
    print("------inner_function-2------")  # 这行不会执行

def outer_function():
    try:
        print("------outer_function-1------")
        inner_function()  # 调用内部函数
        print("------outer_function-2------")  # 因为内部函数抛出异常,这行不会执行
    except Exception as error:
        print("在 outer_function 中捕获到了异常: {}".format(error))
    print("------outer_function-3------")  # 异常被捕获后,程序继续执行

outer_function()
------outer_function-1------
------inner_function-1------
在 outer_function 中捕获到了异常: name 'undefined_variable' is not defined
------outer_function-3------

这个特性使得我们可以在合适的层级(比如主逻辑或调用者)集中处理错误,而不必在每个可能出错的低级函数中都写 try...except。

📝 动手练一练

  1. 编写一个安全的除法计算器:要求用户输入两个数字,计算它们的商。使用异常处理来应对用户输入非数字、除数为零等情况,并给出友好的提示信息。
👉 点击查看参考答案

思路:使用 try...except 包裹核心计算逻辑。ValueError 捕获输入非数字的异常,ZeroDivisionError 捕获除数为零的异常。

# 注意:这里使用固定值演示。实际运行时可将 fixed_a, fixed_b 替换为 input()
fixed_a = "10"  # 模拟用户输入,实际应为 input("请输入被除数: ")
fixed_b = "0"   # 模拟用户输入,实际应为 input("请输入除数: ")

try:
    a = float(fixed_a)
    b = float(fixed_b)
    result = a / b
    print("{} / {} = {}".format(a, b, result))
except ValueError:
    print("错误:请输入有效的数字。")
except ZeroDivisionError:
    print("错误:除数不能为零。")
错误:除数不能为零。

常见错误

错误写法现象或报错原因正确写法
except: (后面无异常类型)虽然能捕获几乎所有运行时异常,但会隐藏真正的问题,不利于排查。过于宽泛,掩盖了真正的问题。明确指定要捕获的异常类型,或用 except Exception: 作为兜底。
except (ValueError, TypeError)
print("出错")
SyntaxError: expected ':'except 子句末尾缺少冒号 :。except (ValueError, TypeError):
print("出错")
将 except Exception: 放在最前面后面的 except ValueError: 等永远不会被执行。Exception 匹配所有异常,第一个匹配到的 except 块会执行。将具体的异常类型(如 ValueError)放在前面,except Exception: 放在最后。
f = open('file.txt')
# ... 操作文件
# 忘记 f.close()
程序运行时可能无报错,但会持续占用系统文件描述符,造成资源泄漏。未在 finally 中确保文件关闭,造成资源泄漏。使用 try...finally 确保文件关闭。

小结

  1. 异常是程序运行时发生的错误信号,会中断正常执行流程。
  2. 使用 try: 包裹可能出错的代码,用 except 异常类型: 来捕获并处理特定异常。
  3. 通过 except ... as err 可以获取异常对象,查看具体错误信息。
  4. 一个 try 可对应多个 except,按顺序匹配。except Exception: 可捕获所有未被前面处理的其他异常。
  5. finally: 块中的代码无论是否发生异常都会执行,常用于资源清理。
  6. 函数内未被捕获的异常会向上一层层的调用者传递,直到被捕获或程序崩溃。

📋 行动清单

  • 将本节所有示例代码手动输入并运行一遍,观察异常被捕获前后的程序输出差异。
  • 修改“捕获多种异常”的示例,尝试触发 NameError,观察输出变化。
  • 编写一个小程序,用 try...except...finally 演示:无论 try 里是否出错,finally 中的清理动作都会执行。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第4章代码包:异常(含1 个 Notebook)
    ZIP · 1KB
    下载