ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python文件操作核心指南:从open参数到with安全读写与编码处理

Python文件操作核心指南:从open参数到with安全读写与编码处理 如果你刚开始写 Python 处理文件大概率会遇到这么几个让我印象极深的场景代码跑完没报错打开文件一看却是空的读取一个文本文件中文全部乱码程序存了几个数据下次重启读回来发现格式全乱。这三个问题基本可以覆盖 80% 新手在文件操作上踩过的坑。这篇文章就围绕 Python 文件操作中的读取、写入、保存这三个核心动作展开把 open 函数的参数、with 的用法、编码处理、路径坑、大文件读取、常见报错这些内容全串起来。不是讲教科书理论而是按我实际调脚本、跑数据的经验来写适合刚学 Python 的初学者也适合写过一阵但一直被文件 IO 折磨的初级开发。先说一句如果你的电脑上还没有装 Python建议先装一个稳定版本比如 3.10 或 3.12装的时候记得勾选 Add Python to PATH不然后面跑命令会遇到一些小麻烦。装好之后随便建一个 .py 文件就能开始今天的操作了。1. 先搞懂文件操作底层读取、写入、保存到底是什么1.1 文件操作的三步本质很多人把文件操作想得太玄乎实际上用 Python 操作文件只需要理解三个动作打开、读写、关闭。打开这一步要做的事情最多。你要告诉系统文件在哪路径、你打算怎么处理它模式、以及用什么样的方式解释里面的字节编码。这三个参数没定好后面全白搭。读写这一步是真正的业务逻辑。读取是把文件里的字节转换成 Python 字符串或 bytes写入是把 Python 里的数据变成字节流塞进文件。这里有个关键概念叫文件指针你可以把它想象成光标读到哪里、写到哪里都由文件指针的位置决定。刚打开文件时指针在开头用 read 读完整个文件后指针跑到末尾再想读就只能读到空字符串除非用 seek 把指针移回去。这个细节特别容易坑人我后面会再提。关闭这一步看似简单却是新手最容易忽略的。文件对象不关轻则占用句柄导致别的地方删不掉、改不了这个文件重则在程序异常退出时把缓冲区里还没落盘的数据全部丢掉。这里要理解一个机制Python 写入文件时数据不会立刻写到硬盘上而是先写进内存缓冲区等缓冲区满了或者调用 close/flush 才会真正写进磁盘。所以“保存”这个概念在代码层面不是点一下 CtrlS而是 flush 和 close 的组合动作。1.2 为什么无条件推荐 with 写法关于打开和关闭我见过不少新手是这样写的f open(data.txt, w) f.write(hello) f.close()这种写法最大的问题在于如果 write 过程中抛了异常close 这行代码根本执行不到文件句柄就一直挂着。更麻烦的是缓冲区里的数据可能根本没落盘你连“写了一半”的数据都看不到。正确做法是用 with 上下文管理器Python 会自动在代码块结束后关闭文件不管里面有没有报错with open(data.txt, w) as f: f.write(hello)这四行代码能解决 90% 的资源泄漏隐患。有人觉得 with 只是语法糖其实它背后调用的是对象的enter和exit方法哪怕 with 内部出现了异常exit也会执行文件关闭逻辑。这就是为什么我在任何场合都坚持只要打开文件无条件用 with。保存文件之后如果想确认数据真的落盘了可以在 with 内部调用 flush或者直接结束 with 代码块交给 close 去处理。2. open() 函数的参数拆解每个参数都不是摆设2.1 模式选择r、w、a 和二进制模式 bopen() 的第二个参数是 mode决定你对文件做什么操作。最常用的几个模式我直接列个表模式含义文件不存在时文件存在时文件指针位置r只读报错 FileNotFoundError正常打开文件开头w只写自动创建清空后打开文件开头a追加写自动创建正常保留内容文件末尾r读写报错正常打开文件开头w读写自动创建清空后打开文件开头a追加并读自动创建正常保留文件末尾rb只读二进制报错正常文件开头wb只写二进制自动创建清空后打开文件开头这里有几个容易误解的点。第一r 是既可以读又可以写但不会创建文件而且当你先读再写或者先写再读的时候文件指针的位置直接决定你读写的位置逻辑非常容易绕晕。我平时如果不是业务强制要求几乎不用 r。第二w 会把原有文件清空这是一个不可逆操作我曾在写数据处理脚本时不小心用 w 模式打开了用户的配置文件等反应过来内容已经被清掉了。所以但凡要修改重要文件建议先备份或者先读内容到内存再写新文件。第三a 模式写内容永远追加在文件末尾适合日志类场景不用自己管理指针。二进制模式很多人一时理解不了。其实文本模式相当于自动帮你做编码转换和解码二进制模式是完全不管编码把文件内容原封不动地以字节序列读出来或写进去。图片、音频、PDF、Excel 这类文件里面不是纯文本结构必须用 b 模式。文本文件也可以用 b 模式但读出来的就是 bytes 而不是 str需要自己 decode。新手只需要记住一条处理文字用默认文本模式处理文件本身带结构的数据用 b 模式。2.2 编码与换行新手最容易踩的坑文本模式打开文件时encoding 参数决定你用什么编码解释文件中的字节。如果你不写这个参数Python 在 Windows 上默认是 GBK在 Linux 和 macOS 上默认是 UTF-8。这俩不一致会导致一个非常经典的问题同一份代码在你自己电脑上跑得好好的部署到服务器上读文件就报 UnicodeDecodeError。解决方式特别简单就是所有涉及到文本文件的 open 操作显式写上 encodingutf-8。目前绝大多数文本文件都是 UTF-8 编码除非你处理的是老旧的 Windows 中文系统生成的 GBK 文件那才需要临时指定 encodinggbk。另外还有两个小细节。第一个是 UTF-8 的 BOM 头有些 Windows 上的编辑器保存文件时会带三个字节的 BOM即 b\xef\xbb\xbf当你用 UTF-8 读取时可能第一行会多出一个看不见的字符比如从 CSV 文件读出来的第一列列名变成了 \ufeffid。处理办法是读取后做字符串清洗或者用 utf-8-sig 编码打开文件Python 会自动帮你把 BOM 吃掉。第二个是换行符。Windows 的文件换行是 \r\nLinux 和 macOS 是 \n。如果你在 Windows 上写出的文件拿到 Linux 上跑经常会有奇怪的连续空行或者末尾多一个 \r。Python 的 open 里有个 newline 参数写 newline\n 可以统一换行符写 newline 可以让换行符不做转换按原始内容保留。数据处理脚本建议显式控制不要依赖平台默认行为。3. 实操过程读取、写入、保存一条龙走通3.1 文件读取read、readline、readlines 怎么选读文件有三种基础方法。read() 一读到底把整个文件内容变成一个字符串。适用于小文件几十 MB 以内的配置、日志、脚本都没问题。readline() 一次只读一行适合逐行处理。readlines() 把整个文件按行拆成列表每行是一个元素适合需要反复访问每一行的场景。这三种方法里我最常用的是直接用 for 循环遍历文件对象这其实是 Python 里最省内存的读取方式with open(large_file.txt, r, encodingutf-8) as f: for line in f: print(line.strip())for line in f 是惰性读取每次只在缓冲区加载一部分内容不会把整个文件塞进内存。而 readlines() 会把所有行一次性读入列表文件一旦上了 GB 级别内存直接爆给看。所以我的习惯是能用 for line in f 就用它除非明确需要随机访问每一行否则别贪图 readlines 的方便。如果需求是“读取 txt 每行的某几列处理后写入另一个文件”这就是一个非常典型的逐行处理场景。比如原文件每行用逗号分隔我只想取出第一列和第三列写入新文件with open(source.txt, r, encodingutf-8) as fin, \\ open(target.txt, w, encodingutf-8) as fout: for line in fin: parts line.strip().split(,) if len(parts) 3: fout.write(f{parts[0]},{parts[2]}\n)这里注意两点第一with 可以同时打开两个文件这个看起来很自然但实际写代码时很多人不知道可以并列写多个 as第二写入的时候要自己加上 \nwrite 方法不会自动补换行符这是新手最容易漏掉的地方。3.2 文件写入与安全保存不让数据丢在半路写文件本身很简单难的是如何可靠地保存。我处理重要数据时写文件有一套固定流程先写入一个临时文件确认写完之后再替换原文件。为什么这么做因为如果直接打开原文件写入一旦程序在写入中途崩溃原文件可能就处于一个半截状态内容被清空了一部分数据却只写了一半。这在日志或配置文件中是非常危险的。import os data 重要的配置内容 temp_file config.tmp target_file config.ini with open(temp_file, w, encodingutf-8) as f: f.write(data) os.replace(temp_file, target_file)os.replace 在 Windows 和 Linux 上都能保证替换操作是原子的要么替换成功要么不动原文件不会出现两个文件同时存在的中间态。这套逻辑理解起来很快但你真正经历过程序崩溃丢数据之后就会明白这个习惯有多重要。另外写日志类的需求推荐用追加模式import time with open(app.log, a, encodingutf-8) as f: f.write(f{time.strftime(%Y-%m-%d %H:%M:%S)} 用户执行了某个操作\n)追加模式天然避免了你手动管理文件指针的问题也不存在误清空的可能。数据量大的场景下,还可以考虑逻辑判断是否超过多少行就自动轮转把旧日志改名新建新日志文件这也是很多框架内部在做的事情。3.3 保存到 Excel、读取图片和爬虫数据落地文件操作并不局限于文本文件。日常开发里大家问得比较多的一个问题就是怎么用 Python 写入 Excel、怎么读取图片的 RGB 值、以及爬虫抓到的数据如何保存。这里都离不开“文件操作”这个底层基础。如果你要操作 Excel 文件直接用 pandas 是最省事的。它底层其实也是做文件读写只不过帮你把 Excel 的二进制结构解析成了 DataFrameimport pandas as pd df pd.read_excel(data.xlsx) # 读取 Excel df[新列] df[销量] * 2 df.to_excel(new_data.xlsx, indexFalse) # 保存 Excel这里要记住to_excel 需要安装 openpyxl没装的话会直接报 ImportError。再比如读取图片像素点的 RGB 值底层也是文件 IO 的范畴但不需要自己解析二进制图片格式用 Pillow 打开图片就行from PIL import Image img Image.open(photo.jpg).convert(RGB) rgb img.getpixel((100, 200)) # 获取 (100, 200) 像素点的 RGB 值此处用 open 打开图片本质上和 open 一个文本文件类似都是在创建文件对象只不过 Pillow 帮你处理了 JPEG 的解码流程。爬虫数据保存也很典型。抓下来的数据如果是结构化的最方便的是用 JSON 格式保存import json data [{name: 张三, age: 18}, {name: 李四, age: 22}] with open(output.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)ensure_asciiFalse 这个参数一定要加否则 json.dump 默认会把中文转成 \u 开头的转义序列打开文件后看到的中文全是 unicode 编码形式。indent2 是让你保存出来的文件有缩进方便人直接看。4. 常见问题与排查技巧实录4.1 报错速查表看一眼就知道问题在哪我把平时遇到最多的几个文件操作报错整理成了表格。遇到问题先对照一下比自己瞎猜快得多。报错信息原因解决办法FileNotFoundError: [Errno 2] No such file or directory文件路径不存在或 r 模式打开了不存在的文件检查路径用 os.path.exists 先判断改用 w/a 模式自动创建UnicodeDecodeError: utf-8 codec cant decode...文件编码不是 UTF-8比如 GBK 编码的中文文件改 encodinggbk或先用其他文本工具确认编码UnicodeEncodeError: gbk codec cant encode...Windows 默认 GBK 写入时遇到生僻字符或 emojiopen 时显式写 encodingutf-8PermissionError: [Errno 13] Permission denied文件被其他程序占用或没有写权限关闭占用程序检查文件是否被 Excel 打开检查读写权限IsADirectoryError: [Errno 21] Is a directory路径指向了一个文件夹而不是文件检查路径确认是文件路径FileExistsError: [Errno 17] File exists用 x 模式创建文件时文件已存在改用 w 模式或先判断文件是否存在这里面 PermissionError 在 Windows 上特别常见因为 Excel 打开文件时会锁定文件句柄Python 后续再去写入就会报这个错。开发脚本时如果发现明明代码没问题但写不进去先想想是不是已经有别的程序占用着这个文件。4.2 三个真实排查现场定位过程比报错更有意思先说编码现场。我帮同事调过一个 csv 读取脚本数据是从某业务系统导出的读出来第一行总是报 UnicodeDecodeError。替换了很多次编码都不对后来用二进制模式打开看了文件头发现并不是 UTF-8 也不是 GBK 的标准开头最后用 notepad 另存为 UTF-8 后问题消失。这个案例的教训是遇到编码问题别只盯着 Python 代码先确认原始文件本身的编码格式。可以用 vscode 或 notepad 打开右下角会有编码提示。第二个是路径现场。有次用户反馈脚本上线后读取不到文件但我本机跑得好好的。后来发现代码用了一个相对路径 “data.txt”而相对路径依赖当前工作目录。本地在项目根目录跑没问题部署后从别的目录启动程序当前目录就变了文件自然找不到。解决办法是代码里用 pathlib 基于脚本文件位置来定位路径from pathlib import Path BASE_DIR Path(__file__).resolve().parent file_path BASE_DIR / data / data.txtPython 3.4 之后 pathlib 是官方推荐路径处理方式比 os.path.join 更直观也更安全。中文路径、Windows 路径分隔符问题在 pathlib 里都能少操心一些。第三个是保存不完整性现场。某次跑了很久的数据处理程序最后要保存一个结果文件但文件大小一直是 0 字节。排查发现代码里只 close 了一个写出的文件对象另一个对象还在内存里没有 flush程序就正常退出了。这个问题不会报错最隐蔽。从那次之后我就养成了用 with 打开所有文件在 with 块内完成全部写入的习惯代码块一结束文件自动关闭数据自然落盘。4.3 几个独家小建议第一写文件的时候如果你真的在意数据不丢失比如财务、监控这类高可靠性场景可以在 close 之后手动调用一下 os.fsync(f.fileno())这会强制把缓冲区数据刷到物理磁盘上。不过日常教程开发里基本用不到了解即可。第二临时文件别手拼随机数用 tempfile 模块生成安全又整洁import tempfile with tempfile.NamedTemporaryFile(w, suffix.txt, deleteFalse) as f: f.write(临时内容) temp_name f.name这在需要生成临时中转文件的场景下特别方便比如刚才说的先写临时文件再 os.replace。第三文件操作报错时不要只盯着报错行学会在报错信息里看 file 和 line 之外的信息。Python 的报错已经写得非常明确一行一行读下来大部分问题自己就能定位。这比盲目加 try except 掩盖问题要高明得多。从最基础的 open 参数到 read、write 的实际使用再到最后应对各种编码和路径问题这套流程走完Python 文件操作基本就算入门了。我自己在写数据处理脚本时文件 IO 永远是最基础也最频繁用到的一环每次遇到问题回过去检查的往往都是那几个原点模式选对了吗、编码写明白了吗、数据真的落盘了吗。你把这几个问题想清楚文件相关的问题就都不算事。
返回列表