Python程序设计强基计划10讲 · 第五讲:文件操作与异常处理——稳健编程的基石
Python程序设计强基计划10讲 · 第五讲文件操作与异常处理——稳健编程的基石作者培风图南以星河揽胜发布时间2026年3月31日适用对象已掌握函数与模块机制的Python学习者前置知识第四讲《函数与模块——代码复用的艺术》标签Python文件操作、异常处理、上下文管理器、logging、CSDN博客引言为什么你的程序总在“关键时刻崩溃”你是否经历过以下场景程序运行到一半突然报错FileNotFoundError所有中间结果丢失用户输入了非法数据程序直接退出毫无提示服务器日志一片空白无法定位线上问题。这些问题的根源在于缺乏对不确定性的处理。现实世界充满意外文件可能被删除、移动或权限不足网络可能中断用户输入永远不可信。文件操作与异常处理正是Python应对这些不确定性的核心机制。它们让你的程序从“脆弱脚本”蜕变为“健壮应用”。本讲将带你系统掌握稳健编程的三大支柱文件操作安全读写文本/二进制数据自动资源管理异常处理优雅捕获错误防止程序崩溃日志记录追踪程序行为助力问题诊断。掌握这些你写的程序将具备不崩溃Graceful Degradation可恢复Error Recovery可追溯Audit Trail一、文件操作安全读写的艺术1.1 基本文件操作流程传统方式不推荐fopen(data.txt,r)contentf.read()f.close()# 容易忘记⚠️ 风险若f.read()抛出异常f.close()不会执行 →资源泄漏手动管理资源违背“自动内存管理”的Python哲学。1.2 上下文管理器with语句自动资源管理推荐方式withopen(data.txt,r,encodingutf-8)asf:contentf.read()# 文件自动关闭即使发生异常✅ 核心优势自动调用__enter__和__exit__确保资源释放文件、网络连接、锁等代码更简洁、安全。黄金法则永远用with open不用裸open()。1.3 文本文件 vs 二进制文件模式说明示例r/w/a文本模式默认open(file.txt, r)rb/wb/ab二进制模式open(image.jpg, rb) 关键区别文本模式自动解码/编码需指定encoding换行符自动转换\n↔\r\n。二进制模式读写bytes对象无任何转换原样处理。✅ 正确指定编码# 必须指定 encoding避免跨平台乱码withopen(data.txt,r,encodingutf-8)asf:...口诀“文本文件加 encoding二进制文件用 rb/wb。”1.4 常用读写方法读取方法返回值适用场景f.read()整个文件字符串小文件f.readline()单行字符串逐行处理f.readlines()行列表需要随机访问行写入方法说明f.write(string)写入字符串f.writelines(lines)写入字符串列表不自动加换行符✅ 安全写入示例lines[Line 1\n,Line 2\n]withopen(output.txt,w,encodingutf-8)asf:f.writelines(lines)# 每行必须自带 \n1.5 处理空行与无效数据真实文件常包含空行注释行如# comment格式错误的数据✅ 健壮读取模板data[]withopen(scores.txt,r,encodingutf-8)asf:forlineinf:strippedline.strip()# 去除首尾空白ifnotstripped:# 跳过空行continueifstripped.startswith(#):# 跳过注释continuetry:scoreint(stripped)data.append(score)exceptValueError:print(fWarning: Invalid data {stripped}, skipped.)原则永远假设输入是脏的。二、异常处理优雅应对错误2.1 异常处理基础try/except/else/finally基本结构try:# 可能出错的代码risky_operation()exceptSpecificErrorase:# 处理特定异常handle_error(e)exceptExceptionase:# 处理其他异常兜底log_error(e)else:# 无异常时执行success_action()finally:# 无论是否异常都执行清理资源cleanup() 实战示例安全除法defsafe_divide(a,b):try:resulta/bexceptZeroDivisionError:print(Error: Division by zero!)returnNoneelse:print(Division successful.)returnresultfinally:print(Cleanup done.)print(safe_divide(10,2))# 成功print(safe_divide(10,0))# 错误2.2 常见内置异常类型异常触发场景处理建议FileNotFoundError文件不存在检查路径、提供默认值PermissionError权限不足提示用户、降级操作ValueError值无效如int(abc)验证输入、友好提示KeyError字典键不存在用get()或预检查IndexError列表索引越界检查长度、用try包裹✅ 最佳实践捕获具体异常而非笼统的Exception# 好try:withopen(filename)asf:...exceptFileNotFoundError:print(fFile{filename}not found.)# 坏掩盖其他错误try:withopen(filename)asf:...exceptException:print(Something went wrong.)# 无法定位问题2.3 自定义异常表达业务逻辑错误当内置异常不足以描述问题时定义自己的异常类。✅ 创建自定义异常classInvalidScoreError(ValueError):成绩不在有效范围 [0, 100]passdefvalidate_score(score):ifnot(0score100):raiseInvalidScoreError(fScore{score}is invalid!)# 使用try:validate_score(150)exceptInvalidScoreErrorase:print(e)# Score 150 is invalid!优势语义清晰可被单独捕获便于文档化。2.4 异常链Exception Chaining当捕获一个异常后抛出另一个异常时保留原始异常信息。✅ 使用raise ... from ...defprocess_file(filename):try:withopen(filename)asf:returnf.read()exceptFileNotFoundErrorase:raiseRuntimeError(fFailed to process{filename})frome# 调用try:process_file(missing.txt)exceptRuntimeErrorase:print(e.__cause__)# 原始 FileNotFoundError作用保留完整的错误上下文便于调试。三、日志记录logging程序的“黑匣子”3.1 为什么不用print()问题logging 解决方案无法区分信息级别支持 DEBUG/INFO/WARNING/ERROR/CRITICAL生产环境难关闭可配置输出目标文件、网络等无时间戳、模块名自动添加元数据性能差字符串拼接延迟求值仅在需要时格式化3.2 logging 基础用法✅ 快速开始importlogging# 配置日志通常在程序入口logging.basicConfig(levellogging.INFO,format%(asctime)s - %(name)s - %(levelname)s - %(message)s,handlers[logging.FileHandler(app.log),logging.StreamHandler()# 同时输出到控制台])loggerlogging.getLogger(__name__)# 使用logger.info(Application started.)logger.warning(This is a warning.)logger.error(An error occurred!)输出示例2026-03-31 10:00:00,123 - __main__ - INFO - Application started. 2026-03-31 10:00:00,124 - __main__ - WARNING - This is a warning. 2026-03-31 10:00:00,125 - __main__ - ERROR - An error occurred!3.3 日志级别与最佳实践级别用途生产环境DEBUG详细调试信息通常关闭INFO程序正常流程开启WARNING潜在问题开启ERROR功能错误开启CRITICAL严重错误程序可能退出开启✅ 最佳实践模块级 loggerlogger logging.getLogger(__name__)不要用print()记录程序状态敏感信息不记日志如密码、身份证号3.4 在异常处理中使用日志importlogging loggerlogging.getLogger(__name__)defload_config(filename):try:withopen(filename)asf:returnjson.load(f)exceptFileNotFoundError:logger.error(fConfig file{filename}not found. Using defaults.)returnDEFAULT_CONFIGexceptjson.JSONDecodeErrorase:logger.critical(fInvalid JSON in{filename}:{e},exc_infoTrue)raise# 重新抛出终止程序关键参数exc_infoTrue→自动记录完整 traceback四、典型误区与避坑指南误区1忽略文件编码# 危险withopen(data.txt,r)asf:...✅修正with open(data.txt, r, encodingutf-8) as f:误区2空 except 块# 绝对禁止try:risky_code()except:pass# 静默失败bug 难以发现✅修正捕获具体异常并记录日志误区3在 finally 中 return# 危险defbad_example():try:returnsuccessfinally:returncleanup# 覆盖 try 中的 return✅修正finally 仅用于清理不返回值误区4日志字符串提前格式化# 低效即使日志级别不够也会格式化logger.debug(Value: str(expensive_function()))# 高效延迟求值logger.debug(Value: %s,expensive_function())五、动手练习构建健壮程序练习1安全文件读取器编写函数read_scores(filename)安全读取文件处理FileNotFoundError过滤空行和注释转换每行为整数处理ValueError返回有效分数列表。练习2带日志的除法函数编写函数logged_divide(a, b)使用 logging 记录操作捕获ZeroDivisionError并记录错误正常时记录结果。练习3自定义异常实战定义异常EmptyFileError在读取空文件时抛出。编写函数process_nonempty_file(filename)若文件为空则抛出该异常。六、本讲小结稳健编程的三大支柱文件操作用with管理资源指定编码过滤脏数据异常处理捕获具体异常自定义业务异常保留错误链日志记录替代print()分级记录助力问题追溯。关键口诀“文件操作 with 走编码指定不能漏。”“异常处理要具体日志分级助 debug。”“程序不怕出错怕的是静默崩溃。”下期预告第六讲《面向对象编程OOP入门——封装、继承与多态》下一讲将开启Python的面向对象之旅类与对象的基本概念封装属性与方法的访问控制继承代码复用的强大机制多态接口统一的灵活性助你从“过程式思维”迈向“对象式思维”原创声明本文为《Python程序设计强基计划10讲》系列第五讲版权归作者所有。互动邀请你在处理文件或异常时踩过哪些坑欢迎评论区分享经验关注我系统提升Python工程能力写出真正健壮的程序