Python文件读写实战:从open()到pandas,讲透编码与大文件处理
发布时间:2026/10/1 19:59:47来源:尧图网络
带过不少学 Python 的新人我观察到一个几乎规律性的现象前两周大家都很顺利print会了条件判断会了列表字典也熟了一切看起来都挺好。等到第一次作业需要把数据处理后保存成文件或者读一个 txt 里的名单挨个处理的时候大批人开始卡壳。不是不会写代码而是对着open()、read()、write()这几个函数不知道从哪里下手网上的教程又各讲各的看得更晕。文件读写这个能力本质上是从在终端里自嗨到真正和数据打交道的分界线。爬虫要存数据办公自动化要碰 Excel数据分析要先读文件再导出结果日志处理要持续追加内容——这些应用场景里文件读写都是地基一样的存在。这篇文章我就按自己多年的使用经验把 Python 文件读写这件事从头到尾拆一遍从open()的底细到路径编码的坑从逐行读大文件到用 pandas 落地 Excel一次讲透。1. 文件读写是 Python 学习中绕不开的那道分水岭1.1 从控制台输出到数据落盘跨过这道坎才算真正入了门很多人第一次接触程序输出用的是print()。这玩意儿确实方便程序跑完结果在终端里印得整整齐齐。但你有没有想过一个问题终端里的内容是一次性的关掉窗口之后什么都没留下。程序一旦结束这些数据就跟没存在过一样。文件读写解决的就是这个核心问题——持久化。把你折腾半天计算出来的结果、从网上抓下来的数据、用户填写的配置信息实实在在写到磁盘上让它们在下一次程序启动时还能被读到。这一点太重要了以至于我可以直接说不会文件读写你写的 Python 程序基本就只能停留在练习题阶段一碰到真实需求就会露怯。我带过的新人里有好几个都是卡在这一步差点放弃。他们当时的心态很有代表性明明每个函数分开看都懂一组合就乱。后来我发现问题不出在智商上是教程没把文件读写这件事讲透。大多数人以为文件读写就是打开-读/写-关闭三步但实际操作中还要考虑文件存不存在、编码是不是 utf-8、写入的内容有没有被缓冲、路径到底怎么拼接。这些细节不看清楚踩坑是必然的。1.2 爬虫、办公自动化、数据分析三大主力场景全绕不开它先说爬虫。写爬虫的人几乎都会遇到同一个需求把抓取到的网页内容或结构化数据存下来。你不可能每次运行爬虫都只把结果打印在屏幕上而是要保存成文本文件或者 CSV方便后续查看和分析。再说办公自动化。这年头 Python 在办公场景里最热门的用法之一就是批量处理 Excel、Word、CSV。热搜词里就有pandas 读写 excel 文件python 写入 excel可见这需求有多普遍。但凡要在程序里操作这些文件第一步永远是把文件打开读进来。最后是数据分析。数据分析的标准流程是读数据、清洗、分析、可视化、导出结论。你从 pandas 里读 CSV、读 Excel分析完再to_csv()或to_excel()落盘这一整套流程的起点和终点都是文件读写。你发现没有这三个热门方向就像三根柱子撑着 Python 应用的大半边天而文件读写就是这三根柱子共同的地基。地基不牢上面盖什么都悬。2. open() 函数全面拆解模式、编码、资源释放一次说透2.1 打开模式对照r、w、a 以及带 和不带 的本质区别Python 里一切文件操作都从open()开始。这个函数看起来简单两三个参数而已但里面藏着不少细节尤其是第一个参数后面跟着的模式字符串。我先把最常用的模式列一张表对应关系一目了然模式含义文件不存在时文件存在时文件指针位置r只读报错正常打开文件开头w只写创建新文件清空原内容文件开头a追加写创建新文件保留原内容文件末尾r读写报错正常打开文件开头w写读创建新文件清空原内容文件开头a追加读创建新文件保留原内容文件末尾rb二进制只读报错正常打开文件开头wb二进制只写创建新文件清空原内容文件开头这张表里最需要刻进脑子里的有两个点。第一w系模式会在打开文件的一瞬间把原文件内容清空不管你之后有没有真正写入任何东西。新手最悲剧的操作就是本来想改一个配置文件用了w打开代码还没跑到写入那一步就先报错了结果原文件已经被清成了空文件。第二号的意思不是追加而是在原有读或写的基础上增加另一方向的操作。r表示可读可写a表示可追加可读好多人以为a里的是追加的意思这是个流传很广的误解。2.2 编码参数为什么 Python 3 默认编码还会遇到乱码open()的第二个容易忽略但是极其关键的参数是encoding。Python 3 的字符串是 Unicode 体系默认使用 UTF-8 编码保存文本文件这本来是好事但实际使用中乱码依然常见原因在于你打开的文件不一定是用 UTF-8 存的。尤其在国内环境很多老系统、旧软件、Windows 记事本默认保存文件用的还是 GBK 或 GB2312 编码。比如你拿到一个对方用 Windows 记事本另存的 txt 文件直接用open(data.txt, encodingutf-8)去读大概率会抛UnicodeDecodeError。我推荐的习惯是只要能确定文件来源的编码就显式写encodingutf-8不确定的时候先尝试 utf-8报错了再换 gbk 试。下面这个写法比较稳try: with open(data.txt, r, encodingutf-8) as f: content f.read() except UnicodeDecodeError: with open(data.txt, r, encodinggbk) as f: content f.read()顺便提一句newline参数。在 Windows 上写文本文件时默认换行会被转成\r\n读的时候又会被转回\n这在某些对格式敏感的场景下会带来麻烦。如果需要精确控制换行行为显式传newline或者按需传newline\n会安全很多。2.3 with 上下文管理器一句话解决忘记 close() 的隐患文件操作有个经典问题打开了文件之后忘了关闭。忘了close()会有什么后果文件描述符泄漏、文件被占用导致其他程序无法访问、写入缓冲区的数据没有真正落到磁盘……这些问题在开发环境里可能不明显一旦放到长期运行的脚本或服务里就会变成诡异的 Bug。with语句就是为这个问题准备的。它会在代码块结束之后自动调用close()哪怕代码块内部抛了异常文件也会被正确关闭。# 不推荐的写法 f open(note.txt, w, encodingutf-8) f.write(hello) f.close() # 万一上面 write 抛异常这里根本执行不到 # 推荐的写法 with open(note.txt, w, encodingutf-8) as f: f.write(hello) # 缩进块结束文件自动关闭异常也能保证释放资源我见过有人纠结用 with 是不是多此一举我的回答是这不是多此一举这是用最简单的方式彻底消灭一类 bug。等你真正经历过文件明明写入了但内容一直是空的、文件被占用删不掉这类问题之后你就知道手动管理生命周期有多不靠谱了。3. 文本文件读取的几种常见姿势按场景选择而不是背函数3.1 read()、readline()、readlines() 三个直觉方法的差异open()打开文件之后下一步就是读取内容。Python 提供了几个名字上很好懂的方法但它们的区别和应用场景搞清楚了能省不少事。read(size)不传参数时一次性读取整个文件内容返回一个字符串传了size就读取指定字节数。readline()读取一行内容包含末尾的换行符也可以传字节数读取该行指定字节部分。readlines()一次性读取整个文件按行分割后返回一个列表每一行是列表中的一个元素。这三个方法里read()和readlines()都涉及一次性读入全部内容逻辑上最爽但内存上最危险。我处理过一个接近 2GB 的日志文件别人用readlines()直接跑内存眼看着涨到 4GB 然后进程被系统干掉。文件越大一次读到底的代价越明显。相比之下readline()每次只处理一行内存占用恒定适合逐行扫描的场景。但它的缺点是写法麻烦要自己维护一个循环。3.2 for 循环直接迭代文件对象处理大文件的优雅方式其实 Python 里最推荐的做法既不是readlines()也不是手动readline()而是直接拿for循环去迭代文件对象本身。文件对象在 Python 中实现了迭代器协议每次循环自动取出下一行底层有自己的缓冲机制既按行处理又不会一次性把整个文件塞进内存内存占用和代码优雅程度都能兼顾。with open(big_log.txt, r, encodingutf-8) as f: for line in f: # 每一行自动去掉末尾换行符再处理 line line.strip() if ERROR in line: print(line.strip())这段代码逐行扫描一个超大日志文件只把包含ERROR的行打印出来。不管日志文件是几十 MB 还是几十 GB内存占用基本恒定因为同一时刻只保留当前这一行。这是我在处理服务器日志时最常用的套路实测下来非常稳。3.3 怎么选从文件大小、数据形态、内存占用三个维度做决策给一个可以直接抄作业的选择思路小文件几 KB 到几 MB怎么读都行追求简单就read()一把梭。需要按行处理的大文件几十 MB 以上优先for line in file迭代读取内存和安全兼顾。文件不大但需要随机访问某几行readlines()转列表后按下标取行理解成本低。底层二进制数据处理或网络流场景用read(size)分块读取控制每次读入的量。我把不同方案的对比整理成了一张表方便你快速查阅方法返回类型内存占用最适合场景f.read()字符串与文件大小成正比小文件整体读取、全文搜索f.readline()字符串恒定逐行处理但不想用 for 迭代的场景f.readlines()列表与文件大小成正比文件不大且需要按行索引访问for line in f迭代器恒定大文件逐行扫描、日志分析4. 写入文件的讲究覆盖、追加、缓冲与指针4.1 write 与 writelines写给新人看的基本习惯读取聊完轮到写入。写入端的基础操作是两个write()和writelines()。write()传入一个字符串直接写入文件。注意它不会自动帮你加换行符你需要显式写出\nwith open(output.txt, w, encodingutf-8) as f: f.write(第一行\n) f.write(第二行\n)writelines()接收一个可迭代对象把每一项逐个写入文件。这里有个新手容易踩的坑writelines()名字里带 lines但它同样不会自动在每行末尾加换行符。如果你传入的列表元素本身不带\n写出来的文件会变成一大坨挤在一起的文本。我自己的习惯是要写多行内容时用列表推导式把所有行拼成带换行符的字符串然后一次write()写完这样代码反而更清晰lines [第一行, 第二行, 第三行] with open(output.txt, w, encodingutf-8) as f: f.write(\n.join(lines)) f.write(\n) # 补上最后一个换行符很多工具会要求文件以换行结尾4.2 flush()、缓冲机制与 close() 之间的微妙关系有一类问题特别诡异代码运行完了程序也没报错但打开文件一看内容是空的或者不全。很多人以为是写入失败了其实是因为缓冲。Python 的文件写入并不是你写一个字符就立刻往磁盘上落一个字符而是先写进内存缓冲区攒到一定量再一次性刷到磁盘。这样做是为了性能因为磁盘 I/O 远比内存操作慢频繁刷盘会让程序慢得没法看。那内容到底什么时候真正落到磁盘三种时机缓冲区满了自动刷、调用flush()主动刷、调用close()关闭文件时自动刷。所以如果你只调用了write()却没有关闭文件或者程序中途异常退出缓冲区里的内容可能就丢了。当你需要写入后立刻被别人读到时比如写日志后马上要 tail 查看可以主动调一下flush()with open(app.log, a, encodingutf-8) as f: f.write(2025-06-01 10:00:00 用户登录成功\n) f.flush() # 强制写盘确保日志立刻可见4.3 追加与覆盖的选择逻辑日志用 a配置更新用 w写文件之前先问自己一个问题这次写入是想从零开始覆盖旧内容还是保留旧内容继续追加选错模式轻则数据覆盖重则文件被清空。日志、流水、埋点记录这类只增不减的场景用追加模式a。好处是简单安全每次打开文件指针自动定位到末尾不会动已经写好的内容。注意a模式下seek()是动不了指针的因为操作系统层面保证你只能从末尾追加。配置文件的更新、程序每轮运行重新生成结果这一类场景用w。它会自动清空旧内容保证文件里只有本次运行的最新数据。但前面强调过w的清空发生在打开那一刻所以如果你还要读原文件内容做参考就不要用w直接打开先r读完再w写入。还有一个容易忽略的细节当你用r或w读写同一文件时读和写共用同一个指针。写入之后文件指针停在写入结束的位置紧接着read()读出来的不是文件开头的内容而是从指针位置继续往后读。这个特性坑过不少人简单说就是读写混合时注意指针位置必要时显式seek(0)回到开头。5. 路径处理与编码问题两个最影响实战的隐性坑5.1 路径拼接告别硬编码字符串用 pathlib 规范操作文件读写逃不开路径。新手最直观的做法是写死一个完整路径open(C:/Users/xxx/Desktop/data/note.txt)。这在当前机器上没问题但一旦代码放到别的电脑、别的操作系统上路径分隔符不一样目录结构不一样立刻崩给你看。稍微进阶一点的人知道用os.path.join()来拼接路径这样能适应不同操作系统的分隔符差异。但今天我更推荐直接上pathlib它在语义化和跨平台方面做得更好。from pathlib import Path # 不用纠结是 / 还是 \直接用正斜杠pathlib 会处理好 data_dir Path(data) file_path data_dir / raw / users.txt # 判断文件是否存在 if not file_path.exists(): print(文件不存在) # 直接打开pathlib 对象可以直接传给 open with open(file_path, r, encodingutf-8) as f: content f.read()pathlib里还有一个我常用的技巧Path.glob()可以批量匹配目录下符合条件的文件配合文件读写能实现很多自动化需求。比如把某个目录下所有.txt文件统一改编码或合并内容几条循环就搞定。5.2 编码乱码排查链路从 UnicodeDecodeError 到修复的完整思路编码问题是真的烦但排查思路其实是固定的。遇到UnicodeDecodeError或者读出来是乱码时我一般按这个顺序排查先用文本编辑器比如 VS Code、Notepad打开文件看一眼右下角的编码提示确认原文件的编码格式。确认后在open()里显式传入对应的encoding。如果文件是 UTF-8 但带着 BOMUTF-8-BOMutf-8可能读不出或首字符带\ufeff可以改用utf-8-sig。如果实在判断不出来用chardet库自动检测编码只做参考别盲信。注意乱码和打不开是两回事。能打开但内容是乱码大概率是读取时用了错误的编码打不开抛UnicodeDecodeError通常是文件内容里混入了当前编码无法解码的字节。还有一种特殊情况同一个文件里混着多种编码的内容。这种情况基本无解我建议让数据源头统一格式或者写入时就一并用 utf-8 规范化。在项目的开始阶段就约定所有文本文件统一 UTF-8能省掉后面无数个加班的晚上。6. 进阶实战csv 模块与 pandas 读写 Excel 的选型6.1 csv 模块轻量级表格数据的标准方案表格数据是文件读写里最常碰到的类型。最基本的 CSV 读写不需要装任何第三方库Python 自带的csv模块就够用。import csv # 读取 CSV with open(users.csv, r, encodingutf-8-sig, newline) as f: reader csv.reader(f) for row in reader: print(row) # 每行是一个列表注意这里的newline参数很多教程里没提。在 Windows 上如果缺了它写入 CSV 时每行后面会多出一个空行因为 csv 模块自己管理换行而默认的换行转换机制又插了一脚两边叠加就多出空行了。我当年写 CSV 时遇到这个诡异现象排查了半天才发现是 newline 参数的问题。如果需要按列名访问数据csv.DictReader和csv.DictWriter更方便它们把每一行映射成字典列名可以是文件头部那一行。with open(users.csv, r, encodingutf-8-sig, newline) as f: reader csv.DictReader(f) for row in reader: print(row[name], row[age])6.2 pandas 读写 Excel办公自动化里的高频动作CSV 毕竟存不了复杂格式很多人实际工作里更多是在处理 Excel 文件。pandas 读写 excel 文件能成为热搜词不是没原因的——这几乎是办公自动化的核心技能之一。pandas 读 Excel 只需要一行代码前提是装好了pandas和openpyxl或者xlrdimport pandas as pd # 读取 Excel指定 sheet 名 df pd.read_excel(销售数据.xlsx, sheet_nameSheet1) print(df.head()) # 处理完后写回新的 Excel 文件 df.to_excel(销售数据_处理后.xlsx, indexFalse, sheet_nameSheet1)关于to_excel我提醒三个细节。第一indexFalse默认不要否则 pandas 会把行号写进 Excel 第一列别人打开你的文件会莫名其妙多一列。第二多个 DataFrame 可以写到同一个 Excel 的不同 sheet用pd.ExcelWriter的上下文管理器。第三pandas 写 Excel 依赖 openpyxl记得提前装好报ModuleNotFoundError是因为缺依赖而不是代码写错了。6.3 表格数据读写的选型建议什么时候 csv、什么时候 pandas很多新手在选择工具时会纠结。我给一个务实的判断标准数据量不大格式简单或者需要兼容老系统用csv模块。它轻量、无依赖、加载快对简单的表格数据完全够用。需要做数据分析、数据清洗、按列筛选计算或者要操作多 sheet 的 Excel 文件直接上 pandas。只需要把大量数据快速写出去、追求性能可以考虑csv模块手写或者pandas两者性能差异在数据量极大时才明显。要保留 Excel 的格式、公式、图表pandas 做不到考虑openpyxl直接操作单元格样式。一句话总结读写只是手段以后要对数据做的事才决定选什么工具。7. 二进制模式与我的踩坑清单7.1 二进制模式处理图片、序列化对象的正确打开方式文本模式处理的是字符串二进制模式处理的是字节。图片、音频、视频、压缩包、pickle 序列化文件这一类东西都必须用rb或wb模式操作。如果你用r模式去读一张图片Python 会尝试按文本解码结果通常是抛UnicodeDecodeError。二进制读写的代码结构和文本模式几乎一样只是读写的数据类型从str变成了bytes# 复制图片文件 with open(source.jpg, rb) as src: with open(copy.jpg, wb) as dst: dst.write(src.read())上面这种src.read()一次读完的方式只适合小文件。大文件复制时我习惯分块读避免整个文件都进内存with open(big_video.mp4, rb) as src: with open(big_video_copy.mp4, wb) as dst: while chunk : src.read(1024 * 1024): # 每次读 1MB dst.write(chunk)Python 对象要持久化存储最标准的方案是pickle模块。它能把列表、字典、自定义对象序列化成字节之后用pickle.load()原样读回来import pickle data {name: 张三, scores: [88, 92, 95]} with open(data.pkl, wb) as f: pickle.dump(data, f) with open(data.pkl, rb) as f: restored pickle.load(f) print(restored)7.2 五个经典坑的报错信息与修复方式逐一复盘最后这部分是我这些年见过、踩过、帮别人擦过屁股的高频问题逐条列出来给你当避雷针。坑一FileNotFoundError文件不存在用r模式打开一个不存在的文件就会报这个错。修复方式先判断文件是否存在或者用try捕获异常。如果是创建新文件直接用w或a模式它们会在文件不存在时自动创建。坑二写进去的内容丢了或没更新大概率是缓冲没刷。如果你用write()之后没close()就强行结束程序、或者程序中途崩了缓冲区里的数据会丢。修复方式用with管理自动关闭或者按需flush()。坑三UnicodeDecodeError/ 读出来是乱码原因就是前面说的编码不匹配。修复方式确认源文件编码显式声明encoding。读出乱码优先检查是不是编码声明错了别急着怀疑代码逻辑。坑四w模式把原来的文件清空了打开模式选错。写之前要读原文件的话先用r读完再考虑w。重要数据文件处理前建议备份或者先用追加模式a测试性写入。坑五CSV 写入后每行之间多了空行Windows 下open()没传newline。修复方式极简单写入 CSV 时加newline这个细节我前文强调过遇到的人都会深深记住。文件读写看似基础实际上藏了不少磨人的细节。我个人这些年最大的体会是与其把各种方法背得滚瓜烂熟不如养成三个习惯——所有open()都配with所有文本操作都显式声明encoding所有大文件都逐行或分块处理。这三个习惯护体文件读写这块的雷你至少已经避掉九成了。
网站建设高端定制企业官网