uniapp 从入门到精通
第2章 2.4 本地存储:数据持久化保存
🎯 开场 3 分钟:为什么要学这个?
上一章我们学会了用 globalData 和 Vuex/Pinia 在内存中存放数据——程序一关,数据全丢。就像你打游戏打到一半没存档,关机后再打开只能从头开始。
你有没有遇到过这些糟心事?
- ✅ 场景 1:写了一个爬虫脚本,爬到一半网断了,重启后它从头开始爬,之前的数据全没了
- ✅ 场景 2:做了一个待办清单 App,添加了 20 条任务,关掉小程序再打开,任务全空了
这感觉就像你辛辛苦苦搬了一整天砖,结果老板说「电脑蓝屏了,数据没保存」——血压瞬间飙升。
学完这一章,你能做到:让程序的数据「活」下来——关机再开机,数据还在。就像真正的游戏存档。
🧱 基础 25 分钟:核心概念
什么是本地存储?
生活类比:想象你有一个日记本。你每天把发生的事情写上去(写入),第二天翻开能读到昨天的内容(读取),撕掉某一页(删除),或者把某一页的内容改一改(修改)。\n\n
\n\n
\n\n
本地存储就是这个日记本——程序把数据写进硬盘,下次启动还能读出来。
为什么不用内存? 内存(变量)断电就没了,本地存储(硬盘/文件)能持久保存。
Python 的本地存储方案
Python 保存数据有几种常见方式,对应不同的「日记本格式」:
方案 1:JSON 文件 —— 最简单、最通用
JSON 就像一本标准格式日记——所有人都能看懂,但只能写「文字和数字」,不能写「图片和自定义对象」。
import json
# 写入数据(想象成"保存日记")
data = {"name": "小明", "age": 18, "skills": ["Python", "uniapp"]}
with open("user.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print("写入完成!")
写入完成!
json.dump() 把字典写进文件,ensure_ascii=False 保证中文不乱码,indent=2 让格式美观。
读取数据(想象成"翻开日记"):
import json
with open("user.json", "r", encoding="utf-8") as f:
data = json.load(f)
print(f"姓名:{data['name']}")
print(f"年龄:{data['age']}")
print(f"技能:{', '.join(data['skills'])}")
姓名:小明
年龄:18
技能:Python, uniapp
方案 2:pickle 文件 —— Python 专属,能存「任意对象」
pickle 就像一个专属日记本,只有你会写会读,别人看不懂。但它只能 Python 用,其他语言读不了。
import pickle
# 写入任意 Python 对象(列表、函数、类实例...)
complex_data = [1, 2, 3, {"key": "value"}]
with open("data.pkl", "wb") as f:
pickle.dump(complex_data, f)
print("pickle 写入完成!")
import pickle
with open("data.pkl", "rb") as f:
loaded = pickle.load(f)
print(f"读取的数据:{loaded}")
读取的数据:[1, 2, 3, {'key': 'value'}]
方案 3:shelve —— 像字典一样方便,文件持久化
shelve 就像一本带索引的日记本,你给它一个标签(key),它帮你存;给同样标签,它帮你读。不用手动管理文件读写。
import shelve
# 打开一个"架子"(类似打开一个数据库文件)
db = shelve.open("my_data")
# 像字典一样存
db["user"] = {"name": "小红", "score": 95}
db["tasks"] = ["吃饭", "睡觉", "写代码"]
db["count"] = 42
# 读取
print(f"用户信息:{db['user']}")
print(f"任务列表:{db['tasks']}")
print(f"计数器:{db['count']}")
# 关闭(重要!不关闭可能丢数据)
db.close()
用户信息:{'name': '小红', 'score': 95}
任务列表:['吃饭', '睡觉', '写代码']
计数器:42
同步 vs 异步 —— 两种「写入方式」
生活类比:你去快递站寄包裹:
- 同步:你站在柜台前等,工作人员打包→贴单→发货→返回,整个过程你都在原地等
- 异步:你把包裹放下,填个单子就走人了,快递站帮你慢慢处理,不耽误你做事
在 uniapp 里,uni.setStorage 是同步(等写入完成才继续),uni.setStorageSync 也是同步,uni.setStorageAsync 是异步。
Python 里对应:
# 同步写入(会阻塞,等写完才往下走)
import json
with open("sync_write.json", "w") as f:
json.dump({"type": "sync"}, f)
print("同步写入完成") # 这行等上面写完才会执行
# 异步写入(用 asyncio,不阻塞)
import asyncio
import json
async def async_write():
await asyncio.to_thread(json.dump, {"type": "async"}, open("async_write.json", "w"))
print("异步写入完成(不阻塞主程序)")
asyncio.run(async_write())
🔥 实战 35 分钟:3 个递进的小项目
项目 1:个人偏好设置保存器(5 分钟)
目标:保存用户的主题偏好,下次启动自动读取。
import json
import os
SETTINGS_FILE = "settings.json"
def load_settings():
"""加载设置,如果文件不存在返回默认设置"""
if os.path.exists(SETTINGS_FILE):
with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {"theme": "light", "font_size": 14, "language": "zh"}
def save_settings(settings):
"""保存设置到文件"""
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
json.dump(settings, f, ensure_ascii=False, indent=2)
# 演示
if __name__ == "__main__":
# 读取当前设置
current = load_settings()
print(f"当前设置:{current}")
# 修改设置
current["theme"] = "dark"
current["font_size"] = 18
save_settings(current)
print("设置已保存!")
# 再次读取验证
verify = load_settings()
print(f"验证保存结果:{verify}")
预期输出:
当前设置:{'theme': 'light', 'font_size': 14, 'language': 'zh'}
设置已保存!
验证保存结果:{'theme': 'dark', 'font_size': 18, 'language': 'zh'}
解释:这就是 uniapp 里 uni.setStorage 和 uni.getStorage 的 Python 版本——一个存,一个读,中间夹着一个文件。
项目 2:待办清单管理器(15 分钟)
目标:从 CSV 文件读取任务列表,添加/完成/删除操作后保存回文件。
import json
import os
import csv
TASKS_FILE = "tasks.json"
def load_tasks():
"""加载任务列表"""
if not os.path.exists(TASKS_FILE):
return []
with open(TASKS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def save_tasks(tasks):
"""保存任务列表"""
with open(TASKS_FILE, "w", encoding="utf-8") as f:
json.dump(tasks, f, ensure_ascii=False, indent=2)
def add_task(title):
"""添加新任务"""
tasks = load_tasks()
new_task = {
"id": len(tasks) + 1,
"title": title,
"done": False,
"created_at": "2024-01-15"
}
tasks.append(new_task)
save_tasks(tasks)
print(f"✅ 已添加任务:{title}")
def complete_task(task_id):
"""标记任务为完成"""
tasks = load_tasks()
for task in tasks:
if task["id"] == task_id:
task["done"] = True
save_tasks(tasks)
print(f"🎉 任务 {task_id} 已完成!")
return
print(f"❌ 未找到任务 {task_id}")
def list_tasks():
"""列出所有任务"""
tasks = load_tasks()
if not tasks:
print("📝 暂无任务")
return
for task in tasks:
status = "✅" if task["done"] else "⬜"
print(f"{status} [{task['id']}] {task['title']}")
def delete_task(task_id):
"""删除任务"""
tasks = load_tasks()
original_len = len(tasks)
tasks = [t for t in tasks if t["id"] != task_id]
if len(tasks) < original_len:
save_tasks(tasks)
print(f"🗑️ 已删除任务 {task_id}")
else:
print(f"❌ 未找到任务 {task_id}")
# 演示
if __name__ == "__main__":
# 初始化一些任务(第一次运行)
if not os.path.exists(TASKS_FILE):
initial_tasks = [
{"id": 1, "title": "完成 Python 作业", "done": False, "created_at": "2024-01-15"},
{"id": 2, "title": "复习期末考试", "done": True, "created_at": "2024-01-14"},
{"id": 3, "title": "给妈妈打电话", "done": False, "created_at": "2024-01-15"},
]
save_tasks(initial_tasks)
print("=== 待办清单 ===")
list_tasks()
print("\n--- 添加新任务 ---")
add_task("学习 uniapp 本地存储")
print("\n--- 完成任务 ---")
complete_task(1)
print("\n--- 删除任务 ---")
delete_task(3)
print("\n=== 最终任务列表 ===")
list_tasks()
预期输出:
=== 待办清单 ===
⬜ [1] 完成 Python 作业
✅ [2] 复习期末考试
⬜ [3] 给妈妈打电话
--- 添加新任务 ---
✅ 已添加任务:学习 uniapp 本地存储
--- 完成任务 ---
🎉 任务 1 已完成!
--- 删除任务 ---
🗑️ 已删除任务 3
=== 最终任务列表 ===
✅ [1] 完成 Python 作业
⬜ [4] 学习 uniapp 本地存储
解释:每次操作后立即 save_tasks(),这就是「持久化」——程序重启后数据还在。
项目 3:学生成绩分析工具(15 分钟)
目标:读取 CSV 成绩单,计算平均分、找出不及格的、生成分析报告保存。
import json
import csv
import os
from datetime import datetime
SCORES_FILE = "scores.csv"
REPORT_FILE = "report.json"
HISTORY_FILE = "history.json"
def load_scores_from_csv():
"""从 CSV 读取成绩"""
if not os.path.exists(SCORES_FILE):
# 创建示例 CSV
sample_data = [
["name", "chinese", "math", "english"],
["张三", "85", "92", "78"],
["李四", "67", "45", "80"],
["王五", "90", "88", "95"],
["赵六", "72", "85", "68"],
]
with open(SCORES_FILE, "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerows(sample_data)
print(f"已创建示例文件 {SCORES_FILE}")
scores = []
with open(SCORES_FILE, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
scores.append({
"name": row["name"],
"chinese": int(row["chinese"]),
"math": int(row["math"]),
"english": int(row["english"]),
})
return scores
def analyze_scores(scores):
"""分析成绩"""
results = []
total_students = len(scores)
total_failed = 0
all_subjects = {"chinese": [], "math": [], "english": []}
for s in scores:
avg = (s["chinese"] + s["math"] + s["english"]) / 3
failed_subjects = []
if s["chinese"] < 60: failed_subjects.append("语文")
if s["math"] < 60: failed_subjects.append("数学")
if s["english"] < 60: failed_subjects.append("英语")
if failed_subjects:
total_failed += 1
for subj in all_subjects:
all_subjects[subj].append(s[subj])
results.append({
"name": s["name"],
"average": round(avg, 1),
"failed": failed_subjects,
"failed_count": len(failed_subjects)
})
subject_avgs = {k: round(sum(v) / len(v), 1) for k, v in all_subjects.items()}
return {
"total_students": total_students,
"total_failed": total_failed,
"pass_rate": round((total_students - total_failed) / total_students * 100, 1),
"subject_averages": subject_avgs,
"students": results
}
def save_report(report):
"""保存分析报告"""
report["generated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(REPORT_FILE, "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
def save_history(report):
"""保存历史记录(追加)"""
if os.path.exists(HISTORY_FILE):
with open(HISTORY_FILE, "r", encoding="utf-8") as f:
history = json.load(f)
else:
history = []
history.append({
"timestamp": report["generated_at"],
"pass_rate": report["pass_rate"],
"total_students": report["total_students"]
})
with open(HISTORY_FILE, "w", encoding="utf-8") as f:
json.dump(history, f, ensure_ascii=False, indent=2)
def print_report(report):
"""打印报告"""
print(f"\n📊 成绩分析报告(生成时间:{report['generated_at']})")
print("=" * 40)
print(f"📌 总人数:{report['total_students']}")
print(f"📌 及格率:{report['pass_rate']}%")
print(f"📌 不及格人数:{report['total_failed']}")
print(f"\n📚 各科平均分:")
for subj, avg in report["subject_averages"].items():
print(f" {subj}:{avg}分")
print(f"\n📋 学生明细:")
for s in report["students"]:
if s["failed"]:
print(f" {s['name']} - 平均{s['average']}分 ❌ 不及格:{', '.join(s['failed'])}")
else:
print(f" {s['name']} - 平均{s['average']}分 ✅")
# 运行
if __name__ == "__main__":
# 1. 读取 CSV
scores = load_scores_from_csv()
print(f"已读取 {len(scores)} 名学生的成绩")
# 2. 分析
report = analyze_scores(scores)
# 3. 保存报告
save_report(report)
save_history(report)
print(f"报告已保存到 {REPORT_FILE}")
# 4. 打印报告
print_report(report)
预期输出:
已读取 4 名学生的成绩
报告已保存到 report.json
📊 成绩分析报告(生成时间:2024-01-15 20:30:00)
========================================
📌 总人数:4
📌 及格率:75.0%
📌 不及格人数:1
📌 不及格人数:1
📚 各科平均分:
hinese:78.5分
ath:77.5分
nglish:80.2分
📋 学生明细:
三 - 平均85.0分 ✅
四 - 平均64.0分 ❌ 不及格:数学
五 - 平均91.0分 ✅
六 - 平均75.0分 ✅
解释:从 CSV 读 → 内存分析 → 存 JSON 报告。这就是数据处理的完整流程:读取→处理→保存。
💪 进阶 20 分钟:常见坑 + 性能小贴士
❌ 坑 1:文件没关闭,数据丢了一半
# ❌ 错误:打开文件后异常退出,没关闭
f = open("data.json", "w")
if some_condition:
raise Exception("崩溃了!")
json.dump(data, f)
f.close() # 这行永远不会执行
# ✅ 正确:用 with 自动关闭
with open("data.json", "w") as f:
if some_condition:
raise Exception("崩溃了!")
json.dump(data, f)
# with 块结束自动关闭,即使异常也关闭
❌ 坑 2:路径不存在,报错 FileNotFoundError
# ❌ 错误:目录不存在会报错
with open("data/subfolder/file.json", "w") as f:
json.dump(data, f)
# ✅ 正确:先创建目录
import os
os.makedirs("data/subfolder", exist_ok=True)
with open("data/subfolder/file.json", "w") as f:
json.dump(data, f)
❌ 坑 3:读文件前没检查存不存在
# ❌ 错误:文件不存在会崩溃
with open("settings.json", "r") as f:
data = json.load(f)
# ✅ 正确:先检查
import os
if os.path.exists("settings.json"):
with open("settings.json", "r") as f:
data = json.load(f)
else:
data = {"theme": "light"} # 默认值
❌ 坑 4:JSON 中文乱码
# ❌ 错误:中文变成 Unicode 转义
data = {"name": "小明", "city": "北京"}
with open("data.json", "w") as f:
json.dump(data, f) # 中文会变成 \u5c0f\u660e
# ✅ 正确:设置 ensure_ascii=False
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
❌ 坑 5:大文件一次性读写,内存爆炸
# ❌ 错误:大 JSON 文件一次读入内存
with open("big_data.json", "r") as f:
data = json.load(f) # 1GB 文件直接爆内存
# ✅ 正确:用 ijson 流式处理,或分块读写
# 对于超大文件,考虑用数据库而非 JSON
⚡ 性能小贴士:批量写入比多次单条写入快 100 倍
import time
# ❌ 慢:每条单独写
start = time.time()
for i in range(1000):
with open("slow.json", "w") as f:
json.dump({"index": i}, f)
print(f"逐条写入耗时:{time.time() - start:.2f}秒")
# ✅ 快:全部收集完一次性写
start = time.time()
all_data = [{"index": i} for i in range(1000)]
with open("fast.json", "w") as f:
json.dump(all_data, f, indent=2)
print(f"批量写入耗时:{time.time() - start:.2f}秒")
🔧 调试技巧:用 logging 而非 print
import logging
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
logger.debug("这是一条调试信息")
logger.info("这是普通信息")
logger.warning("这是警告")
logger.error("这是错误")
# 日志可以写进文件,print 只能打屏
✏️ 练习题
练习 1(2 分钟):保存并读取你的信息
- 输入:运行代码,修改
name为你的名字 - 预期输出:屏幕上打印
你好,XXX! - 提示:参考项目 1 的
load_settings和save_settings函数
import json
def save_user(name):
with open("my_info.json", "w", encoding="utf-8") as f:
json.dump({"name": name}, f, ensure_ascii=False)
def greet():
with open("my_info.json", "r", encoding="utf-8") as f:
data = json.load(f)
print(f"你好,{data['name']}!")
save_user("小明")
greet()
练习 2(2 分钟):添加一个「已完成」过滤
- 输入:在项目 2 的
list_tasks()中加一个show_done参数 - 预期输出:
list_tasks(show_done=False)不显示已完成任务,list_tasks(show_done=True)显示全部 - 提示:用
if task["done"] == show_done过滤
练习 3(3 分钟):给成绩分析加最高分统计
- 输入:在项目 3 的分析结果中加入「各科最高分」
- 预期输出:报告里多显示
最高分:语文 90,数学 92,英语 95 - 提示:在
analyze_scores里用max()函数
练习 4(5 分钟):合并两个待办清单
- 输入:两个 JSON 文件
list_a.json和list_b.json,各有任务列表 - 预期输出:合并后的
merged.json,去除重复 ID - 提示:用
set()记录已见过的 ID
练习 5(3 分钟):修复这个报错
- 输入:运行下面代码,遇到
JSONDecodeError - 预期输出:修复后能正确读取
- 提示:文件内容不是有效 JSON,手动创建一个正确的 JSON 文件
import json
with open("bad_data.json", "r") as f:
data = json.load(f)
print(data)
📝 作业:做一个「阅读进度追踪器」
需求:记录你阅读书籍的进度,下次打开能继续。
功能点:
1. 添加书籍(书名、作者、总页数)
2. 记录阅读进度(当前页数)
3. 查看所有书籍及进度
4. 退出后重新打开,进度不丢失
加分项:
1. 计算并显示完成百分比
2. 进度超过 80% 标记为「即将完成」
3. 数据存储在 books.json 文件中
验收标准:
- 能运行 python books.py 启动
- 添加书籍后关闭程序,再打开能看到之前添加的书
- 代码有适当注释
📚 总结 + 资源
本文学到的 3 个核心点:
- JSON 是最通用的存储格式 —— 跨语言、人类可读、够用就行
- 文件操作一定要用
with,自动关闭不丢数据 - 每次修改后立即保存,做到真正的数据持久化
延伸学习资源:
- 📖 Python 官方文档:json 模块 —— 最权威的 JSON 操作参考
- 📖 《Python 编程:从入门到实践》第十一章 —— 文件和异常
- 🎬 B站:Python 文件操作实战 —— 搜索「Python 文件读写」有大量视频
互动钩子:
你在写爬虫或数据分析时,是怎么保存中间结果的?是用 JSON、CSV 还是直接存数据库?评论区聊聊,优质回复优先置顶!
下章预告:
学会了本地存储,是时候做一个真正的完整页面了——下一章我们会做一个小明的「个人主页」,把学到的页面跳转、数据传递、本地存储全部串起来,做一个能跑的小项目!

评论(0)