2.1 文件读写与 JSON 持久化


2.1 文件读写与 JSON 持久化

本节摘要:程序里的变量住在内存里,程序一结束就被系统收走;文件住在磁盘上,关机都在。本节讲 Python 读写文件的标准姿势——with 语句三步走,再引入 JSON 这门"数据通用语",把轻记账的列表和字典原样存进磁盘、下次启动原样读回。学完这节,v0.2 的数据在两次运行之间彻底活了下来,这也是第五章之前项目唯一的持久化方案。

本节的能力清单

阅读完本节,你应当能够:

  1. 用 with 语句打开、读写、关闭文本文件,解释为什么不手动 close
  2. 在文本模式与二进制模式之间做正确选择
  3. 用 json 模块把 Python 对象序列化成文件、再反序列化回内存
  4. 给轻记账加上"启动加载、改动落盘"的持久化逻辑

问题:内存是帐篷,磁盘才是房子

v0.1 的账目存在列表里,列表住在内存。内存有个特点:程序的命有多长,变量的命就多长。终端一关,操作系统把内存整个回收,账目尸骨无存。要跨过"程序生命周期"这道墙,只能把数据写到磁盘文件里。这看起来是个新话题,其实第一章的模块文件早就是它——你写的代码本身,就是最成功的持久化案例。

图 2-1 一笔账从内存到磁盘的往返旅程

图 2-1 一笔账从内存到磁盘的往返旅程

文件读写三步走,with 一步到位

裸写文件操作有三步:打开、读写、关闭。漏掉关闭,数据可能还滞留在缓冲区没真正落盘,文件也会被长期占用。Python 用 with 语句把关闭这件事自动化:

with open("notes.txt", "w", encoding="utf-8") as f: f.write("轻记账的第一行日志\n") f.write("第二行\n") # 离开 with 块,文件自动关闭,缓冲自动清空

模式参数决定打开方式,常用就三个:

模式 含义 文件已存在时 典型场景
w 写入 清空重写 导出全新报告
a 追加 接在末尾 记日志
r 读取 ——(默认) 读配置

encoding 参数务必写 utf-8,Windows 默认编码会把中文写成乱码,这是中文教程读者最常见的翻车点。读文件用循环最稳,逐行处理不怕大文件:

with open("notes.txt", "r", encoding="utf-8") as f: for line in f: print(line.strip()) # strip 去掉行尾换行符

JSON:两种语言之间的通用语

直接把列表写进文件得到的是 Python 的"展示格式",读回来要费劲解析。JSON 是专门的数据交换格式,长得几乎就是 Python 字典的写法,json 模块负责双向翻译:

import json ledger = [ {"item": "早饭", "amount": 12.5, "date": "2026-09-01"}, {"item": "地铁", "amount": 4.0, "date": "2026-09-01"}, ] # 写出:对象 → 文本 → 文件 with open("ledger.json", "w", encoding="utf-8") as f: json.dump(ledger, f, ensure_ascii=False, indent=2) # 读入:文件 → 文本 → 对象 with open("ledger.json", "r", encoding="utf-8") as f: loaded = json.load(f) print(loaded[0]["item"]) # 早饭 —— 完整的列表和字典回来了 print(type(loaded)) # <class 'list'>

ensure_ascii=False 让中文原样保存而非变成转义序列;indent=2 让文件带缩进,人肉检查方便。有个硬规矩要背:**JSON 的键必须是字符串**,元组会变列表、None 变 null、True 变 true——存之前想清楚哪些类型过不了这道桥,不确定就先 dump 一份打开看看。

演练:给轻记账装上持久化

把"读入 - 修改 - 写回"接进 v0.1 的主循环:

import json import os DATA_FILE = "ledger.json" def load_ledger(): if not os.path.exists(DATA_FILE): return [] # 第一次运行,还没有文件 with open(DATA_FILE, "r", encoding="utf-8") as f: return json.load(f) def save_ledger(ledger): with open(DATA_FILE, "w", encoding="utf-8") as f: json.dump(ledger, f, ensure_ascii=False, indent=2) def main(): ledger = load_ledger() # 启动即恢复现场 print(f"已加载 {len(ledger)} 笔历史账目") running = True while running: command = input("指令(记 / 查 / 退):") if command == "记": item = input("用途:") amount = float(input("金额:")) ledger.append({"item": item, "amount": amount}) save_ledger(ledger) # 每记一笔立即落盘 print("已保存") elif command == "查": for i, record in enumerate(ledger, start=1): print(f"{i}. {record['item']} {record['amount']} 元") elif command == "退": running = False print("账本已关闭,数据已保存") main()

运行两次验证:第一次记两笔账后退出;第二次启动,终端打出"已加载 2 笔历史账目"。数据跨过了程序的生死线。变式一:给每笔账加日期字段,用下一节要讲的 datetime 自动填当天。变式二:手工把 ledger.json 改坏——删掉一个方括号——再启动,程序会在 json.load 处崩溃,这个坑由下一节的异常处理来填。

易错点清单

  • 忘记 encoding="utf-8":Windows 下中文乱码或读写报错,养成每处 open 都带的习惯
  • 模式用错:想追加却用了 w,历史数据被清空且无法找回——写之前想清楚是重写还是续写
  • json.load 读到坏文件:文件不存在抛 FileNotFoundError,内容损坏抛 JSONDecodeError,都要接
  • 先 dump 后才改内存:改了列表忘了落盘,退出后"白记",落盘动作要贴着每次改动

本节要点回顾

  • 内存命短,磁盘命长,跨运行的数据必须落文件
  • with 语句自动关闭文件,模式 w 清空 / a 追加 / r 读取,编码统一 utf-8
  • JSON 是数据通用语,json.dump 与 json.load 完成对象与文件的双向翻译
  • 轻记账 v0.2 持久化完成:"启动读入、改动写回"三拍节奏
  • 坏文件会让启动崩溃,异常处理是下一节的主题

数据活得下来了,可手滑一下程序还是当场去世。下一节给代码装安全网——try / except 异常处理。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U