代码语言

知识点思维导图

28 个知识节点

Python(19) - 文件与 IO 操作

读完后,你应能完成以下任务:

  • 绘制“Python(19) - 文件与 IO 操作 / 先给锚点:读文件的心智模型”的关键对象与数据流,解释“关键边界:Python 默认没有"异步读文件"这个日常选项。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Python(19) - 文件与 IO 操作 / open 的两个核心参数:mode 和 encoding”设计正常与异常输入,验证“因为 Python 的 open 不指定 encoding 时,会用操作系统默认编码:Linux/Mac 通常是 utf-8(碰巧没事),但 Windows 默认是 GBK,读写中文就会乱码或直接抛 UnicodeDecodeError。”,输出首个偏差位置与回归测试结果。
  • 实现“Python(19) - 文件与 IO 操作 / mode:读 / 写 / 追加 / 二进制”的最小代码或配置,检验“⚠️ 头号坑:"w" 模式一打开就立即清空整个文件,哪怕你后面一个字都没写。”,输出命令、结果与 Diff,并说明不适用边界。

你在 Node 里读写文件靠 fs.readFileSync / fs.promises.readFile,解析 JSON 靠 JSON.parse,处理 CSV 还得装个 papaparse。到了数据处理阶段,读文件这件事会变得很频繁——读训练数据、读配置、把分析结果写回磁盘。本篇解决三个最常见的落地问题:怎么用 Python 安全地读写文本文件、怎么读写 JSON、怎么读写 CSV。好消息是 Python 把 JSON 和 CSV 都做进了标准库,连库都不用装。

一、先给锚点:读文件的心智模型

Node 读文件你大概率写过这两种:

// JS / Node:同步读
const fs = require("fs")
const content = fs.readFileSync("data.txt", "utf-8")   // 一次性读成字符串

// JS / Node:Promise 异步读
const content = await fs.promises.readFile("data.txt", "utf-8")

Python 的标准写法只有一种姿势——with open(...),你在第 9 篇(异常处理与上下文管理)已经见过:

先用对照表建立直觉:

JS / Node Python 说明
fs.readFileSync(path, "utf-8") open(path).read() 一次性读成字符串
fs.writeFileSync(path, data) open(path, "w").write(data) 覆盖写
fs.appendFileSync(path, data) open(path, "a").write(data) 追加写
JSON.parse(str) json.loads(str) 字符串 → 对象
JSON.stringify(obj) json.dumps(obj) 对象 → 字符串
papaparse(第三方) import csv(标准库) CSV 读写
path.join(a, b) Path(a) / b 拼路径

关键边界:Python 默认没有"异步读文件"这个日常选项。Node 里大家习惯 await readFile,但 Python 标准库的 open 就是同步的,而且这在数据处理脚本里完全够用、也是惯例。异步文件 IO(aiofiles 等)是 Web 框架里的特殊需求,不是本篇重点。


二、open 的两个核心参数:mode 和 encoding

open 最常用就两个参数:第二个 mode(怎么打开)和 encoding(按什么编码解码文本)

2.1 mode:读 / 写 / 追加 / 二进制

对照 Node 的 flag,其实是一回事,只是 Python 的字母更短:

Python mode Node flag 含义
"r" "r" 只读
"w" "w" 覆盖写(清空)
"a" "a" 追加写
"rb" / "wb" "r" 配 buffer 二进制读 / 写

⚠️ 头号坑:"w" 模式一打开就立即清空整个文件,哪怕你后面一个字都没写。想保留原内容续写,用 "a"。这和 Node 的 "w" flag 行为一致,但前端同学常忘。

2.2 encoding:中文乱码的罪魁祸首

为什么必须显式写?因为 Python 的 open 不指定 encoding 时,会用操作系统默认编码:Linux/Mac 通常是 utf-8(碰巧没事),但 Windows 默认是 GBK,读写中文就会乱码或直接抛 UnicodeDecodeError。Node 里你习惯了第二个参数传 "utf-8",Python 这边对应的就是 encoding="utf-8",养成肌肉记忆。


三、四种读取方式:什么时候读全部,什么时候逐行

Node 里你基本就是 readFile 一次性读完。Python 给了更细的粒度,核心区别在"小文件全读" vs "大文件逐行流式读"

最推荐、也是最 Python 的写法——直接把文件对象当成可迭代对象,一行一行流式遍历

这正对应 Node 里"小文件 readFile、大文件用 stream"的取舍:

// Node 大文件要手动上 stream,写法明显更重
const rl = readline.createInterface({ input: fs.createReadStream("huge.log") })
for await (const line of rl) {
  console.log(line)
}

Python 这边一个 for line in f 就达到了同样的"流式、省内存"效果,是它比 Node 顺手的地方。

记忆:read() 全读成一个字符串,readlines() 全读成一个列表,for line in f 逐行流式(大文件用这个)。


四、写文件:write 与 writelines

⚠️ 坑:write / writelines 都不会帮你加换行符,需要换行得自己在字符串里写 \n。这和 Node 的 fs.writeFile 一样,但容易和 print(默认带换行)搞混。


五、JSON:标准库自带,但要分清 4 个函数

前端处理 JSON 就两个函数:JSON.parseJSON.stringify。Python 的 json 模块(标准库,不用装)也是这个思路,但多了一组带文件的版本,新手最容易混。

记住一个规律:s 的处理"字符串"(string),不带 s 的直接处理"文件对象"

我要做的事 JS Python 记忆
JSON 字符串 → 对象 JSON.parse(str) json.loads(str) loads = load string
对象 → JSON 字符串 JSON.stringify(obj) json.dumps(obj) dumps = dump string
直接从文件读成对象 先 readFile 再 parse json.load(f) load 接文件对象
对象直接写进文件 先 stringify 再 writeFile json.dump(obj, f) dump 接文件对象

5.1 字符串版:loads / dumps

dumps 有两个前端一定要记的参数

  • ensure_ascii=False:不写它(默认 True),中文会被转成 \uXXXX 形式的 Unicode 转义码——比如 "你好" 会被序列化成 "\u4f60\u597d"。要正常显示中文必须加这句。这是 Python 特有的坑,JS 的 JSON.stringify 不会干这事。
  • indent=2:美化缩进,等价于 JS 的 JSON.stringify(obj, null, 2)

5.2 文件版:load / dump(不带 s,直接接文件)

对照 Node,你会发现 Python 的文件版少写一步:

// Node:读 JSON 得两步——先 readFile 拿字符串,再 parse
const loaded = JSON.parse(fs.readFileSync("config.json", "utf-8"))
// 写也是两步——先 stringify,再 writeFile
fs.writeFileSync("config.json", JSON.stringify(config, null, 2))

⚠️ 类型映射边界:JSON 的 true/false/null 解析成 Python 的 True/False/None(首字母大写、nullNone);反过来 Python 的 None 会序列化成 JSON 的 null。另外 Python 的 dict 键如果是数字,dumps 后会变成字符串键——和 JS 对象一样,JSON 的键永远是字符串。


六、CSV:标准库 csv 模块

CSV(逗号分隔的表格文本)在数据处理里到处都是。前端通常装 papaparse,Python 直接 import csv 即可。

6.1 读 CSV

最常用 csv.DictReader——把每一行读成字典,用表头当 key,比按下标取值清晰得多:

如果不想用表头、就要原始的每行列表,用 csv.reader

6.2 写 CSV

⚠️ CSV 三大坑(前端高频):

  1. 读出来的值全是字符串——row["age"]"30" 不是 30,要算数得先 int() / float()
  2. newline="" 不能省——Python 官方文档明确要求写 CSV 时加它,否则 Windows 下每行之间会多出空行。
  3. CSV 是纯文本、没有类型和嵌套结构。真要做表格分析(筛选、分组、聚合),别用 csv 硬撸,下一阶段的 Pandas 才是正解(pd.read_csv 一行搞定,详见第 21 篇)。csv 模块适合简单的逐行读写。

七、路径处理:用 pathlib 代替字符串拼接

前端拼路径用 path.join("a", "b"),避免手写 / 在不同系统出错。Python 现代写法是 pathlib.Path它把路径做成对象,用 / 运算符拼接,很优雅:

对照 Node:

Node Python (pathlib)
path.join(a, b) Path(a) / b
path.extname(p) p.suffix
path.basename(p) p.name
fs.existsSync(p) p.exists()
__dirname Path(__file__).parent

⚠️ 相对路径坑(前后端通病):open("data.txt") 里的相对路径,是相对于运行脚本时所在的工作目录(cwd),不是脚本文件本身的位置。在不同目录下 python xxx.py 启动,可能就找不到文件。稳妥做法:用 Path(__file__).parent / "data.txt" 锚定到脚本自身位置。


八、最容易踩的坑(前端视角汇总)

  1. "w" 模式一开就清空文件——想续写用 "a"
  2. 不写 encoding="utf-8"——Windows 下默认 GBK,中文乱码或报 UnicodeDecodeError。读写文本永远显式带上。
  3. json.dumps 中文变 \uXXXX——加 ensure_ascii=False 才正常显示中文。
  4. load/loads 分不清——带 s 的吃字符串,不带 s 的吃文件对象。
  5. CSV 值全是字符串——数字要手动 int()/float();写 CSV 别忘 newline=""
  6. 相对路径相对的是 cwd 不是脚本——用 Path(__file__).parent 锚定。
  7. 大文件别 f.read() 一次性读——用 for line in f 流式逐行,不然爆内存。

九、总结

  • 先给锚点:读文件的心智模型:关键边界:Python 默认没有"异步读文件"这个日常选项。
  • open 的两个核心参数:mode 和 encoding:对照 Node 的 flag,其实是一回事,只是 Python 的字母更短:
  • 四种读取方式:什么时候读全部,什么时候逐行:Node 里你基本就是 readFile 一次性读完。
  • 写文件:write 与 writelines:⚠️ 坑:write / writelines 都不会帮你加换行符,需要换行得自己在字符串里写 \n。
  • JSON:标准库自带,但要分清 4 个函数:Python 的 json 模块(标准库,不用装)也是这个思路,但多了一组带文件的版本,新手最容易混。
  • CSV:标准库 csv 模块:CSV(逗号分隔的表格文本)在数据处理里到处都是。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“文件与 IO 操作”中,需要同时满足“先给锚点:读文件的心智模型”与“open 的两个核心参数:mode 和 encoding”。给定正文约束“异步文件 IO(aiofiles 等)是 Web 框架里的特殊需求,不是本篇重点。”,哪些判断保持了原有处理机制?多选
2“文件与 IO 操作”出现偏差:“在“文件与 IO 操作 / mode:读 / 写 / 追加 / 二进制”中,即使不满足“对照 Node 的 flag,其实是一回事,只是 Python 的字母更短”,结果与副作用仍会保持不变。”已成为实际行为。围绕“mode:读 / 写 / 追加 / 二进制”与“encoding:中文乱码的罪魁祸首”,哪些判断能定位被改变的职责或边界?多选
3评审“文件与 IO 操作”方案时,验收条件包含“Python 给了更细的粒度,核心区别在"小文件全读" vs "大文件逐行流式读"。”。关于“四种读取方式:什么时候读全部,什么时候逐行”与“写文件:write 与 writelines”的哪些决策符合正文机制?多选