本节摘要:模块把一组相关函数收进可复用的文件,异常处理让程序在遇到错误时不再崩溃。本节讲清楚模块的导入方式、标准库的利用、
try/except/finally的执行流程,以及自定义异常的设计。通过"安全的除法器"和"统计工具模块"两道练习,把代码组织与错误防御的能力练出来。
阅读完本节,你应当能够:
import x、from x import y、import x as a 的适用场景try/except 捕获指定异常,用 finally 保证清理随着你的程序变大,把所有函数堆在一个文件里会变得难以管理——找一个函数要在几百行里翻,多人协作时还会互相覆盖。模块化是最自然的解法:把相关功能收进各自的文件,需要时再导入复用。
异常处理解决的是另一个问题:程序运行时总会遇到意外——用户输错了类型、文件找不到、网络断了。如果对这些错误没有任何准备,程序会直接崩溃退出,用户体验极差。异常处理让你能预判可能出错的地方,在错误发生时优雅地处理,而不是让整个程序挂掉。
这两者放一起讲,是因为它们共同构成了"程序健壮性"的两根支柱:模块化让代码结构清晰、易于维护;异常处理让代码在运行时容错。一个结构清晰但一遇错误就崩的程序,和一个能扛错误但代码乱成一团麻的程序,都算不上好工程。
在 Python 里,任何一个 .py 文件就是一个模块。假设你有一个存放统计函数的设计,写成一个文件,里面定义了 mean、median 等函数:
# 概念示意:统计工具模块的内容 def mean(nums): return sum(nums) / len(nums) def median(nums): s = sorted(nums) n = len(s) mid = n // 2 return s[mid] if n % 2 == 1 else (s[mid - 1] + s[mid]) / 2
在另一个文件里,你可以通过 import 把这些函数拿过来用。模块名就是文件名去掉 .py。
import stats # 导入整个模块,用时要带模块名 print(stats.mean([1, 2, 3])) from stats import mean, median # 只导入需要的名字,调用时更简洁 print(mean([1, 2, 3])) import stats as st # 起短别名,常用于名字长的库 print(st.mean([1, 2, 3]))
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
import x |
命名空间清晰,不冲突 | 调用要带模块名,稍长 | 大多数场景,推荐默认 |
from x import y |
调用简洁 | 可能与本地同名冲突 | 只用少数几个名字时 |
import x as a |
名字短 | 降低可读性 | 社区约定俗成的库(如 import numpy as np) |
⚠️ 常见坑:
from x import *会把模块里所有名字倒进当前命名空间,极易和你的变量重名。永远别用这种写法。
Python 自带一个庞大的标准库,很多常见任务不用自己造轮子:
import math # 数学函数:sqrt, pi, log import random # 随机数:randint, choice, shuffle import datetime # 日期时间处理 import os # 操作系统接口 import json # JSON 读写 import collections # 高级容器:Counter, defaultdict
写代码前先想想"标准库里有没有现成的",能省下大量重复劳动。
try: num = int(input("输入一个数:")) result = 10 / num print(f"结果是 {result}") except ValueError: print("输入的不是数字") except ZeroDivisionError: print("不能除以零") finally: print("不管对错,这句都会执行")
执行流程清晰:try 块里放可能出错的代码;一旦某行抛出异常,立即跳到匹配的 except;finally 块无论是否发生异常都会执行,常用于资源清理。
除了捕获内置异常,你也可以主动抛出,甚至定义自己的异常类:
class InvalidScoreError(Exception): """分数不合法时抛出""" pass def grade(score): if not 0 <= score <= 100: raise InvalidScoreError(f"分数必须在 0-100,得到 {score}") return "合格" if score >= 60 else "不合格"
自定义异常的好处是语义清晰——InvalidScoreError 比 ValueError 更能说明发生了什么。
# 坏:捕获所有异常,连键盘中断都被吞 try: do_something() except: # 等价于 except BaseException pass # 好:只捕获你预期且能处理的异常 try: do_something() except (ValueError, TypeError) as e: print(f"输入有问题:{e}")
裸 except 会掩盖你没预料到的 bug——程序"看起来没崩",实际逻辑已经错了,调试时极难发现。
⚠️ 常见坑:
except Exception比except:稍好(至少放过KeyboardInterrupt),但仍然太宽。尽量只捕获你知道怎么处理的特定异常类型。
不是所有异常都该捕获。原则是:捕获你能处理的,传播你不能处理的。
def read_config(path): try: with open(path) as f: return f.read() except FileNotFoundError: # 能处理:用默认配置 return "{}" # 其他异常(如权限不足)不捕获,让它向上传播
如果你在每层都把异常吞掉,问题就被层层掩盖,最终无法定位根因。
__name__ == "__main__" 的作用写模块时常用这个判断:当文件被直接运行时 __name__ 等于 "__main__",被导入时不等于。这样可以让模块既能被导入复用,又能独立运行测试:
def mean(nums): return sum(nums) / len(nums) if __name__ == "__main__": # 只在直接运行时执行测试代码,被导入时不执行 print(mean([1, 2, 3, 4])) # 2.5
with 语句与资源管理文件、网络连接这类资源,用完必须关闭。with 语句能保证即使中间出错也会自动清理:
# 推荐:with 自动关闭 with open("data.txt") as f: content = f.read() # 离开 with 块,f 自动关闭 # 不推荐:手动关闭,中间出错就漏关 f = open("data.txt") content = f.read() f.close()
💡 关键直觉:
with本质是try/finally的语法糖。凡是"打开-使用-关闭"模式的资源,都用with,把"忘记关闭"这类错误从语法层面杜绝。
题面:写一个函数 safe_divide(a, b),返回 a/b 的结果。如果 b 是零或非数字,捕获异常并返回 None,调用者据此判断是否成功。
思路点拨:把除法放进 try,捕获 ZeroDivisionError 和 TypeError。返回 None 表示失败是一种简单约定,但调用方要记得检查。
参考骨架:
def safe_divide(a, b): try: return a / b except (ZeroDivisionError, TypeError): return None print(safe_divide(10, 2)) # 5.0 print(safe_divide(10, 0)) # None print(safe_divide(10, "x")) # None
题面:把"平均值、中位数、众数"三个函数写成一个统计模块(概念上是一个独立文件),然后在"主程序"里导入它,对一组数据计算这三个统计量。要求:当数据为空时,函数抛出自定义异常 EmptyDataError。
思路点拨:自定义异常继承 Exception;每个函数开头判断数据是否为空。众数可以用 collections.Counter 找出现次数最多的元素。
参考骨架:
# 统计模块(概念示意) class EmptyDataError(Exception): pass def mean(nums): if not nums: raise EmptyDataError("数据不能为空") return sum(nums) / len(nums) def median(nums): if not nums: raise EmptyDataError("数据不能为空") s = sorted(nums) n = len(s) mid = n // 2 return s[mid] if n % 2 == 1 else (s[mid - 1] + s[mid]) / 2 # 主程序(概念示意:另一处导入后使用) data = [1, 2, 2, 3, 4] try: print(f"均值 {mean(data)}, 中位数 {median(data)}") except EmptyDataError as e: print(f"统计失败:{e}")
💡 思考延伸:真实的模块组织里,统计函数放进一个文件,主程序放进另一个文件,用
import连接。这里为了在一个代码块展示,把它们写在一起。理解"职责分离到不同文件"这个思想是关键。
题面:写一个程序,反复要求用户输入一个正整数,直到输入合法为止。处理三类错误:输入不是数字、输入是负数或零、用户主动中断(Ctrl+C)。每类错误给不同提示,合法时打印"收到:N"并退出。
思路点拨:while True 循环里 try/except,分别捕获 ValueError(非数字)和 KeyboardInterrupt(用户中断)。合法性(正数)用 if 判断。这是把异常处理和循环结合的综合题。
参考骨架:
while True: try: s = input("输入一个正整数:") n = int(s) if n <= 0: print("必须是正整数") continue print(f"收到:{n}") break except ValueError: print("不是有效的整数") except KeyboardInterrupt: print("\n用户取消") break
⚠️ 常见坑:
KeyboardInterrupt不继承自Exception,而是继承自BaseException,所以except Exception抓不到它——这正是它能用来"打断程序"的原因。如果你确实想捕获它,要单独写except KeyboardInterrupt。

包和模块是什么关系? 模块是单个文件,包是带 __init__ 的文件夹,把一组模块按主题归类。判断要不要建包的信号:模块数量超过五六个、或者出现了"统计相关的放一堆、IO 相关的放一堆"的自然分组。初学阶段不必急着建包,先养成"一个职责一个模块"的习惯,包是模块多了之后水到渠成的组织方式。
异常处理和返回错误码,哪种更好? 各有适用面。错误码(返回 None 或 -1)的优点是调用者必须显式面对失败,缺点是返回值通道被占用、错误信息量少;异常的优点是正常路径干净、错误信息可以携带完整上下文(堆栈),缺点是调用者可能忘记捕获。Python 社区的倾向很明确:真正的异常情况用异常,可预期的"普通失败"(查不到、不存在)看情况——库的公共接口倾向异常,内部小工具用返回值也无妨。关键是一致:同一个函数别一半错误走返回值、一半走异常。
else 子句是干什么的? try/except 还有个少见的 else:它只在 try 块没有抛出任何异常时执行。价值在于把"可能出错的代码"和"依赖其结果的后续代码"分开——如果后续代码写进 try 里,它自己抛出的异常也会被同一个 except 误捕获,错误归因就乱了。见到长 try 块时,考虑把不出错的部分挪进 else,是提升可读性的小技巧。
循环输入那道题为什么 continue 不写也行? 是的,except ValueError 处理完会自然回到 while True 的下一轮,continue 在这里是显式表达"这轮作废"。两种写法都对,但加上 continue 后,if n <= 0 分支的意图更醒目——读者一眼看出"非法输入被拦在这里"。可读性常常就差这么一个多余的词。
补充一道小练习收尾:写一个 read_int(prompt) 函数,反复要求输入直到拿到合法整数并返回它,把"健壮的输入读取循环"那道题的逻辑收进函数里。你会发现封装后主程序只剩一行干净的调用——这正是函数与异常处理配合的价值:脏活被收进一个可复用的单元,主流程保持清爽。做完再想想:这个函数该放在哪?如果第二个程序也要读整数,就该把它放进你的工具模块,从此一劳永逸——这就是"模块是装函数的文件"最直接的红利。
import 复用,是代码组织的基础单元。import x,少数名字用 from x import y,长名用别名;永远别用 import *。try/except/finally:捕获要精确,别裸 except,否则会掩盖真正的 bug。InvalidScoreError 比 ValueError 更能说明发生了什么。with:自动清理,杜绝"忘记关闭"。if __name__ == "__main__" 让模块既能导入又能独立测试。下一章我们迈向更大的组织单元——类与对象,把数据和操作数据的方法绑在一起。
补充说明:本章练习涉及的报错信息、边界输入与修复思路,都建议同步记录到你的错题本上;错题本是贯穿全书的学习工具,附录第四节给出了具体的复盘格式,配合使用效果最好。坚持记录三周,你会明显感觉到面对报错时的心态从慌乱变成有条理的排除动作,这种稳定性正是练习题真正想练出来的底层能力。
再次强调模块与异常的配合原则:边界函数负责拦截与翻译错误,内部函数负责抛出带语义的异常,主入口统一兜底。这套分工在小练习里看似多余,但一旦你的程序超过两百行、函数超过十个,它就是维持可维护性的骨架。练习时把这条原则当作检查清单逐条对照,比事后重构便宜得多。