news 2026/10/24 2:52:09

Python文件读写与异常处理实战指南:从编码到容错的完整学习记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python文件读写与异常处理实战指南:从编码到容错的完整学习记录

第26天,Python学习进度条终于走到文件读写和异常处理这个坎儿上。这俩东西在教程里往往被放在不起眼的角落,但实际写起脚本、处理数据、做小工具,几乎每一个真实程序都逃不开它们。我自己的感受是,前面函数、列表、字典如果说是搭骨架,那文件读写就是让程序真正“活”起来的第一步——程序跑完能把结果留下来,能读取外部输入,才不是自娱自乐。而异常处理,则是从“写着玩”迈向“能用”的一道分水岭。

这篇记录我按自己第26天的实际学习思路来梳理:前半场主攻文件读写的各种细节和坑,后半场把异常处理的完整套路捋清楚,最后用一个小案例把两者串起来。适合正在学Python、刚过完基础语法想往实战走的朋友,也适合复习时发现自己对文件操作一知半解的初学者。今天这篇里讲的踩坑记录,都是我自己写代码试出来的,不是背文档。

1. 第26天该学什么:先把学习路数理清楚

1.1 一个朴素但有效的打卡路线

第26天这个位置,说尴尬也尴尬。前25天如果按常见学习路径走,已经覆盖了变量、数据类型、流程控制、函数、列表推导式、模块导入这些基础内容。但这个阶段的代码基本是“一次性”的——跑完就没了,数据只存在内存里。想做个像样的东西出来,第一步就是要让程序能和外部世界打交道,说的直白一点:读写文件。

我给自己定的这阶段路线很直白:

  • 文本文件的读和写,搞懂open、close、with
  • 弄清楚编码为什么会导致乱码
  • 用json搞定结构化数据的存储和加载
  • 把异常处理try、except、finally用熟练
  • 写一个小工具,把文件读写和异常处理结合起来

这五步走完,你已经具备写“有实际价值”的程序的基本能力了。我见过很多初学者卡在“学完语法但做不出东西”这个阶段,很大程度上就是因为文件读写没学好——想写个词频统计、日志分析都动不了手,自然觉得编程没意思。

1.2 为什么偏偏是文件读写和异常处理

我自己的体会是,这两块内容属于典型的“用着不难,但坑特别多”的部分。你看PDF文件,Read、Write、Append、二进制模式,模式参数就那么几个,背下来很容易。但真正上手才会撞到编码问题、路径分隔符问题、文件不存在问题、读写速度问题。如果只背API不看细节,写出来的代码要么跑不起来,要么跑起来以后莫名其妙报错。

异常处理就更不用说了。很多初学者觉得 try-except 就是“把有可能出错的代码包起来”,这个理解没错,但如果只学这一层,写出来的代码往往把异常全吞掉,出了问题连日志都没有。第26天正是建立“程序凒壮性”意识的起点——不是不报错,而是报错的时候你能知道错在哪、程序不死掉、用户看得懂提示。

所以今天这篇不是把官方文档翻译一遍,而是把我实操过程中真正有用的细节、真正踩过的坑,按场景重新组织一遍。

2. 文件读写核心:open函数到底怎么用才靠谱

2.1 模式参数别死记硬背,用场景推

Python里打开文件的入口就是 open(),最常见的用法长这样:

file_obj = open("data.txt", "r", encoding="utf-8") content_text = file_obj.read() file_obj.close()

第一个参数是文件路径,第二个参数是模式,第三个参数是指定编码。模式参数是最容易搞混的,我看到很多人靠背表格:r是读、w是写、a是追加、rb是二进制读……背是背下来了,但一换场景就懵。

这里我推荐一个更不容易出错的理解方式——不要按字母去背,按你要干什么去推:

  • 你要读内容,就用 "r",这是最安全的选择,文件不存在会报错,不会误删数据
  • 你要从零开始写新内容,就用 "w",它会直接覆盖原文件,文件不存在会新建
  • 你要在文件末尾追加内容,就用 "a",原有内容保留,新内容接在后面
  • 你要处理图片、音频、压缩包,这些非文本的二进制文件,才用 "b" 系列: "rb"、"wb"、"ab"

"r+"、"w+"、"a+" 这类的带上加号的模式,是同时允许读写。我实际用下来的建议是:新手阶段尽量不要碰带+的模式,逻辑很容易绕晕。比如 "w+" 会先清空文件再让你读,很多人忘记这一点,以为可以边读边改,结果一打开内容全没了。

一个小事:默认模式是 "r",也就是你不写第二个参数,程序会按只读模式打开,文件不存在时直接抛 FileNotFoundError。所以宁可写全,不要依赖默认值。

2.2 编码问题:乱码背后的真正原因

编码问题可能是文件读写里最影响体验的坑,没有之一。很多人第一次跑 open("test.txt", "r") 读出来一堆乱码,第一反应是文件坏了,其实大概率是编码对不上。

Python 3 里 open() 如果不指定 encoding 参数,会使用平台默认编码。我自己之前在 Windows 上跑就遇到过,系统默认编码经常是 GBK,而文件是 UTF-8 编码保存的,两边对不上,读出来就是乱码。解决方式很简单,打开文件时显式指定编码:

content_text = open("来源文件.txt", "r", encoding="utf-8").read()

反过来也有一种情况,文件本身是 GBK 编码,你强制用 utf-8 去读,也会乱码甚至直接报 UnicodeDecodeError。这时候把 encoding 换成 "gbk" 就好。这个“双方对齐”的思路比死记什么编码表重要得多。

写入的时候同样要显式指定编码。我之前写配置文件时吃过亏:只写了 open("out.txt", "w"),结果文件写出来是 GBK 编码,拿到别的环境一读就乱。后来统一养成习惯,读写都写全 encoding="utf-8"。

提示:写文本文件时如果看到 UnicodeEncodeError,十有八九是编码问题。不要尝试用什么“忽略错误”的参数硬编码去糊弄,先排查是不是编码没对齐。

2.3 路径问题:反斜杠、正斜杠和相对路径

路径这一块,Windows 用户踩的坑最多。Windows 的路径分隔符是反斜杠 \,但在 Python 字符串里反斜杠是转义符,所以如果你写:

file_obj = open("C:\Users\test\data.txt")

Python 会把 \U、\t 这些当成转义字符处理,轻则路径错误,重则直接报错。有三种常用解法:

  • 写成双反斜杠:"C:\Users\test\data.txt"
  • 写成原始字符串:r"C:\Users\test\data.txt"
  • 统一用正斜杠:"C:/Users/test/data.txt"(Windows 也认正斜杠)

我个人的习惯是尽量用相对路径,也就是相对于当前工作目录的路径。比如脚本和文件在同一目录下,直接写文件名就行。这样代码拿到任何机器上都不会因为盘符不同而挂掉。

3. with open:上下文管理器的正确姿势

3.1 为什么不能只靠 close()

相信你注意到了,我在上面第一段代码里手写了 close()。这么做的问题在于:如果程序在 read() 或 write() 中间抛了异常,close() 那一行根本没机会执行,文件句柄就泄漏了。文件句柄开着不放,最直接的表现是文件被占用——在 Windows 上你连删都删不掉,在 Linux 上也可能导致资源耗尽。

正确的做法是用 with 语句:

with open("data.txt", "r", encoding="utf-8") as file_obj: content_text = file_obj.read()

with 语句会在代码块执行完毕后自动关闭文件,不管中间有没有异常。这是 Python 里最典型的“上下文管理器”用法。我自己的习惯是:凡是 open() 打开文件,一律用 with,除非有特别极端的场景。这个习惯形成之后,我几乎没有再遇到文件句柄被占用的问题。

3.2 读文件的几种姿势

读文件也不是只有 read() 一种选择。根据文件大小和用途,至少要掌握三种方式:

# 一次性读入全部内容,小文件适合 content_text = file_obj.read() # 按行读取,得到一个列表,每行是一个元素 lines_list = file_obj.readlines() # 迭代读取,逐行处理,大文件首选 for one_line in file_obj: print(one_line)

第三种逐行迭代的方式最值得养成习惯,因为文件特别大的时候,read() 会把所有内容装进内存,非常容易撑爆内存。我记得有次处理一个几个 GB 的日志文件,readlines() 直接内存吃满,换成 for 循环逐行读,内存占用瞬间降到几十 MB。就这个体验,值得点个赞。

3.3 写文件时注意刷新和缓冲

写文件的时候有一个细节很多人忽视:写入不一定立即落盘。Python 的写入操作会先写到内存缓冲区,缓冲区满了或者文件关闭时才真正写到磁盘。如果程序在中途崩溃,会发现文件存在但内容是空的或者不全。

所以如果你有“边写边保存”的需求,可以考虑在关键节点调用 flush(),把缓冲区内容强制写入磁盘。但也不要过度使用,频繁写盘会影响性能。折中的办法是:程序结束前写完整逻辑,with 会自动帮你做收尾。如果程序需要长期运行,比如写日志,那么在每次写完后 flush 是比较稳妥的。

4. 用json搞定结构化数据存取

4.1 dump和dumps到底选哪个

学会了文本文件的读写,下一个自然的需求就是存结构化数据。比如你有一个字典,里面有学生名字、成绩、列表,直接存成字符串再读回来就乱七八糟了。这时候就该 json 模块登场。

json 模块里有两对函数特别容易混淆:

import json data_dict = {"name": "小明", "scores": [90, 88, 100]} # dumps:把Python对象转成字符串 json_str = json.dumps(data_dict, ensure_ascii=False) print(json_str) # dump:把Python对象转成字符串,并直接写入文件 with open("data.json", "w", encoding="utf-8") as file_obj: json.dump(data_dict, file_obj, ensure_ascii=False) # 反向操作 # loads:把字符串转回Python对象 restore_data = json.loads(json_str) # load:从文件读内容并转回Python对象 with open("data.json", "r", encoding="utf-8") as file_obj: load_data = json.load(file_obj)

记忆方法很简单:带 s 的是 String(字符串),不带 s 的是跟文件打交道。dumps 和 loads 是“内存到内存”,dump 和 load 是“内存到文件”。

4.2 关于 ensure_ascii 的坑

上面代码里的 ensure_ascii=False 值得单独讲一下。json 模块默认会把非 ASCII 字符转成 \uXXXX 这种转义形式,比如“小明”会变成 "\u5c0f\u660e"。这么做的好处是兼容性极强,坏处是你打开文件看的时候满屏转义符,并且文件体积变大。

如果你存的是中文内容,还是希望人能直接读,那就在写入的时候加上 ensure_ascii=False。这个参数不写的话中文全变转义符,读的时候还能还原,但看着实在痛苦。

load 和 loads 也有一个细节需要留心:当文件内容为空时,json.load 会直接抛异常。如果文件可能被清空或者缺失,建议先做判断,再加载,或者用异常处理兜底。这也是完整的工程思维的一部分。

4.3 配置文件用什么格式:json、yaml还是ini

这里分享一点做小工具时的选型经验。如果是自用的小脚本,我强烈推荐 json 做配置格式:Python 原生支持,读写代码量最小,存储结构清晰,稍大一点也足够用。YAML 可读性更好、支持注释,但需要安装第三方库,处理不当时还有解析歧义。ini 文件虽然有 configparser 但表达能力有限,嵌套结构没办法优雅表达。

所以 80% 的自用小工具场景,json 是最省心的选择。如果项目需要给外部用户提供配置能力、需要写注释,再考虑 YAML 也不迟。

5. 异常处理的完整套路

5.1 先理解什么情况下才会触发异常

异常不是“玄学”,它是程序运行时遇到错误时抛出的信号。常见的异常类型包括:文件不存在时抛 FileNotFoundError,访问列表越界抛 IndexError,字典里键不存在抛 KeyError,类型和值不匹配抛 TypeError 和 ValueError。这些都是 Python 内置的异常类,互相之间存在继承关系。

新手最容易犯的错是用一大段 try 把所有代码包住,然后 except 后面不写任何类型:

try: with open("data.txt", "r") as file_obj: content_text = file_obj.read() number_value = int(content_text) except: print("出错了")

这问题在哪?第一,它会捕获所有异常,包括你根本没预料的错误,结果错误信息被吞掉,你连调试头绪都没有。第二,它不会告诉你具体错在哪个环节,文件读失败和转换失败混在一起,排查起来效率极低。

5.2 针对性的捕获,分类型处理

正确的做法是分类型捕获:

try: with open("data.txt", "r", encoding="utf-8") as file_obj: content_text = file_obj.read() number_value = int(content_text.strip()) except FileNotFoundError: print("文件不存在,请检查路径") except ValueError: print("内容不是有效的数字")

这样每个错误都有对应的处理逻辑。让人一眼看出哪里可能会出错、出错以后的行为是什么。在工程上这叫“针对性异常处理”,也是别人读你代码时最容易感受到专业度的地方。

5.3 用 else 和 finally 理顺逻辑

try-except 还可以搭配 else 和 finally,它们的作用经常被网上的教程省略。我自己用下来感觉非常有用:

try: with open("data.json", "r", encoding="utf-8") as file_obj: config_data = json.load(file_obj) except FileNotFoundError: print("配置文件缺失,使用默认配置") config_data = {} except json.JSONDecodeError: print("配置文件损坏,使用默认配置") config_data = {} else: print("配置文件加载成功") finally: print("配置加载流程执行完毕")

else 块里的代码只在 try 块没有抛出异常时执行。可以把“成功后的额外操作”放进去,这样不会把成功逻辑和可能出错的逻辑混在一起。finally 块则无论有没有异常都会执行,适合放清理操作,比如关闭连接、释放锁。

这里有一个我踩过多次的坑:如果 try 里已经用 with 处理了文件,finally 里不需要再 close(),多此一举。但如果用的是更底层的资源(比如 socket、临时目录),在 finally 里做清理是标准实践。

5.4 自定义异常和主动抛异常

到了第26天,可以开始接触一个更进阶的内容:自定义异常。虽然它不像前面那些基础一样每天必须用,但理解它能让你的程序结构上一个台阶。

使用方法很简单,继承内置的 Exception 类就行:

class ConfigValidateError(Exception): pass def validate_config(config_data_dict): if "timeout" not in config_data_dict: raise ConfigValidateError("配置缺少timeout字段")

自定义异常最大的好处,是把“业务层的错误”和“语法层的错误”区分开。别人读到你的代码时,看到 raise ConfigValidateError,就能立刻理解这是业务流程里的校验失败,而不是网络问题、文件问题。这种“语义化报错”的意识,越早建立越好。

6. 实战小项目:写一个带容错的配置读取工具

6.1 一个真实高频的需求

文件读写和异常处理最典型的结合场景,就是读取配置文件。我几乎每次写自用小工具都会重复这个需求:程序启动时读一个配置文件,里面存放可调整的参数,比如超时时间、需要扫描的目录、输出格式等。如果配置文件不存在或者格式坏了,程序不应该崩溃,而是用默认值继续运行,并给出提示。

这里我直接给出一个可落地的完整示例,核心是把 JSON 配置读取做成了一个独立的函数:

import json def load_config(file_path, default_config): try: with open(file_path, "r", encoding="utf-8") as file_obj: config_data = json.load(file_obj) if not isinstance(config_data, dict): raise ValueError("配置格式应为JSON对象") except FileNotFoundError: print(f"警告:配置文件 {file_path} 不存在,使用默认配置") return default_config except json.JSONDecodeError: print(f"警告:配置文件 {file_path} 解析失败,使用默认配置") return default_config except ValueError as error: print(f"警告:配置文件内容异常:{error},使用默认配置") return default_config else: # 把默认配置和用户配置合并,用户配置优先 merge_config = dict(default_config) merge_config.update(config_data) return merge_config default_settings = {"timeout": 5, "output_dir": "./out", "verbose": False} config = load_config("settings.json", default_settings) print("当前配置:", config)

这个例子虽然短,但已经把今天讲的核心点全部用上了:

  • 用 with 管理文件句柄,不会泄漏
  • 显式指定 utf-8 编码,避免平台差异
  • 三个不同的异常类型分开处理,不复用一个大 try 块
  • else 分支放成功逻辑,不会被误报异常污染
  • 默认配置合并机制,让程序在文件缺失时依然能正常运行

直接复制到你的项目里,改一改默认配置就能用。

6.2 还可以怎么扩展

如果想把这段代码再往前推一步,可以考虑几个方向:

一是支持自动创建配置文件。检测到文件不存在时,把默认配置直接写入文件,方便用户手动修改,下次启动时就能读到有效配置。二是增加配置的单元测试。把异常情况(文件不存在、文件内容非法、字段缺失)分别写进测试用例,跑一遍确认程序行为符合预期。三是配合今天的异常处理知识,把 load_config 抛出的业务异常定义成自定义异常类型,让上层调用时能精确捕获。

我自己的实践体会是,小工具的价值不在于功能多复杂,而在于它的失败模式是可预测的。用户少了一个配置文件、改错一个字段类型,程序都不会崩溃成一堆红字,这就是今天这些基础知识带来的回报。

7. 常见问题与排查技巧实录

7.1 高频问题速查表

把这段时间积累的高频问题整理成一个表格,遇到问题对着查:

问题现象可能原因建议解法
读文本出现乱码读写编码不一致读写时都显式指定 encoding="utf-8",或改为与文件相同的编码
文件打不开,报 FileNotFoundError路径错误或文件不存在用相对路径,确认脚本工作目录,打印当前目录定位
写文件后内容丢失写模式用了 "w" 被覆盖,或没有 close/flush追加用 "a" 模式,重要文件先备份,用 with 自动关闭
把中文字符写入 json 变成 \uXXXX默认 ensure_ascii=True写入时加 ensure_ascii=False
用 r+ 打开文件读取为空忽略了 w+ / r+ 的截断行为新手先不用带+的模式,读和写分开处理
异常被吞没有日志except 里只写了 pass至少打印 error 信息,或写入日志文件
程序报 UnicodeDecodeError文件编码与指定编码不匹配用二进制模式读取,或尝试其他编码测量工具

这些问题的共同点在于:不是语法不会,而是没有建立起“文件状态”的意识。文件打开时的状态是只读、覆盖还是追加,写入缓冲区是否刷新,编码是否对齐,每一条都对应着程序运行时的一个状态。

7.2 三个让效率翻倍的排查技巧

第一个技巧:在报错信息里看上下文。Python 的 traceback 是从下往上读的,最后一行是错误类型和错误信息,往上翻是关键代码行。很多人只看最后一行的“FileNotFoundError”就慌了,其实往上两行就能看到到底是哪个文件路径出错了。

第二个技巧:打印当前工作目录。这招在排查“文件找不到”时特别有用。用 os.getcwd() 打印当前目录,确认脚本觉得“当前在哪里”,很多路径困惑瞬间解开。

import os print(os.getcwd())

第三个技巧:用二进制模式确认文件内容。如果文件读出来总是乱码,先别急着改代码,用编辑器或者命令行以二进制方式打开文件看原始字节序列,能非常直观地判断文件的真实编码。顺便说一句,很多编码问题的本质是“用错误的方式解释字节”,确认原始字节比瞎猜编码快得多。

7.3 这些坑一定要亲身踩一遍

最后说几句可能不太中听、但非常真实的建议:光看别人的踩坑记录没用,一定要亲手把这些问题触发一遍。我自己的学习路径是,看完教程后专门写了一个故意出错的脚本:故意不 close 文件、故意不指定编码读 GBK 文件、故意用 w 模式覆盖重要文件。在安全的环境下把这些错误穷举一遍,眼见报错信息长什么样,之后真正在工作中遇到就不慌了。

这个过程大概会花掉一两个小时,但换来的收益很大。学编程到第26天,早就过了“看视频点头”的舒适区,亲手碰一遍错误、亲手解决一个 bug,比刷十节网课都管用。这种“主动试错”的学习方式,也是我到了这个阶段觉得自己写代码明显更稳的重要原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 2:52:07

接口测试用例设计实战:从入参边界到业务场景全覆盖

做过一段时间接口测试的人基本都会遇到同一个问题:功能用例写得再熟练,一转到接口层面就不知道怎么下手。对着接口文档看了半天,最后只能把每个字段的“正常值、异常值”机械罗列一遍,用例看着不少,上线后还是漏测&…

作者头像 李华
网站建设 2026/10/24 2:51:38

C++移动语义实战指南:从右值引用到std::move的完整笔记

我用这门课的PPT内容为主线,结合自己课后的复现代码和实际项目里的使用经验,整理成一份带实践视角的完整笔记。如果你正处在"会写拷贝构造、但看不懂别人代码里为什么到处是std::move"的阶段,这份笔记能帮你把这些概念全部串起来。…

作者头像 李华
网站建设 2026/10/24 2:50:27

港股18A生物医药IPO:13.8亿营收背后的盈利质量与估值拆解

港股生物医药板块最近又热闹了。一家做抗体类生物药的企业传出冲刺港交所的消息,公开报道里的数据很直接:9个月营收13.8亿元,净利润1.2亿元,背后站着的股东既有国际知名私募机构,也有A股老牌上市药企。普通读者看到“净…

作者头像 李华
网站建设 2026/10/24 2:49:04

CLion嵌入式开发跳转失效修复:从编译数据库到索引重建

搞嵌入式开发的老铁们应该都有过这种经历:工程代码写了一堆,突然想顺手点一下某个函数的定义,Ctrl鼠标左键按下去,结果不是纹丝不动,就是给你跳到个“No definition found”的空空页面,瞬间血压就上来了。我…

作者头像 李华
网站建设 2026/10/24 2:49:01

钙钛矿组件产线检测三大挑战与激光测试方案解析

钙钛矿组件的产业化这几年从实验室走向GW级产线,速度比晶硅当年快得多,但产线上的检测环节却一直处于“有苦说不出”的状态。我在现场跟线调试过多次钙钛矿检测设备,最深的一个感受是:钙钛矿组件的检测难点,从来不只是…

作者头像 李华
网站建设 2026/10/24 2:48:57

gprMax与FDTD探地雷达仿真:建模、网格步长与GPU加速

简介:gprMax 是面向探地雷达(GPR)电磁波传播数值建模的开源软件,基于有限差分时域(FDTD)方法在三维空间求解麦克斯韦方程组,也可用于其他电磁传播仿真场景。内容适合关注地质雷达、无损探测、天…

作者头像 李华