首页 / 资讯中心 / 文章详情

Python数据持久化实战:从文件读写到JSON、CSV与SQLite

Python数据持久化实战:从文件读写到JSON、CSV与SQLite ★ FEATURED ARTICLE
你有没有遇到过这种场景写了一个挺完整的程序跑起来一切正常但只要一关掉窗口用户刚填的数据、配置好的参数、积累的日志全都归零了。下次再启动程序又“失忆”了一切从头再来。这就是典型的“程序没有记忆”的状态。想让程序拥有“记忆”靠的就是文件操作与数据持久化这两块基本功。这一节我会从最底层的文件读写讲起到 JSON、CSV、SQLite 几种主流持久化方案再带大家用一个不到 50 行的记账本例子把这些知识串起来最后把系统层面常见的文件权限、磁盘排查、编码报错这些坑挨个说一遍。适合正在学编程、想写点真正能用的工具脚本或者被各种文件读写报错折腾过的新手朋友。内容以 Python 为主线但其中关于路径、编码、权限的思路放到 Java、C#、Go 里同样适用。1. 先搞明白程序为什么需要“记忆”1.1 内存与磁盘的根本区别程序运行期间数据都存在内存里。内存这东西速度快得吓人但有个致命弱点——断电即失。不管你用的是便宜的 DDR4 还是最新的 DDR5只要断电里面的一切都消失得干干净净连“恢复出厂设置”的机会都没有。磁盘就不一样了。硬盘、固态硬盘上的数据断电之后还在。这就是内存和磁盘这对“冤家”各自的分工内存负责“临时运算”磁盘负责“长期保存”。程序想拥有“记忆”本质就是要把内存里的重要数据按一定的格式搬到磁盘上等下次程序启动时再从磁盘把这些数据重新加载回内存。我见过不少初学者包括当年的我脑子里根本没有“持久化”这个概念写出来的记账工具、待办清单、配置管理脚本一关终端数据就没了然后反复跑来问“我的数据哪去了”。其实数据从来就没真正“存在”过它只是躺在了内存这个临时的落脚点里。1.2 哪些场景必须做持久化不是所有数据都需要落地到磁盘。判断标准很简单这个数据关了程序之后还有没有用必须持久化的典型场景包括用户注册信息、订单记录、博客文章、配置参数、操作日志、缓存数据、训练好的模型参数。这些数据如果丢失轻则用户体验崩盘重则直接造成经济损失。完全不需要持久化的场景也有临时运算的中间变量、函数调用栈、循环里的计数器和临时拼接的字符串。这些东西用完就扔丢了你也不心疼反而每次启动都重新计算才是常态。还有一种容易被忽略的情况有些数据自己不需要持久化但别的程序可能需要。比如一个爬虫程序抓了 10 万条数据它自己可能只用当前的这批但如果没有落盘网络一抖动或者程序一崩溃这 10 万条就白抓了。养成“关键数据随手落盘”的习惯能帮你躲过特别多意外。1.3 持久化方案的选择思路文件持久化听起来就一个词——“存文件”但真要选方案的时候门道不少。核心选择标准有四个数据量多大数据结构复杂不复杂要不要多程序共享需不需要按条件查询数据量只有几十 KB 到几 MB结构简单选纯文本文件就够。数据有嵌套结构比如字典套列表、列表套字典选 JSON 最顺手。数据是规规矩矩的表格形状每一行字段都一样选 CSV 最合适Excel 直接打开就能看。数据量大到几十 MB 以上或者需要频繁增删改查、需要按条件过滤就别折腾文件了直接上 SQLite 这种嵌入式数据库。我之前做过一个“植物浇水提醒”的小工具一开始图省事用纯文本存每行存“植物名字|浇水日期”。后来想加“上次施肥日期”“生长备注”格式立刻崩了改解析逻辑改到怀疑人生。换成 JSON 之后加字段只是多写一个键值对的事解析根本不需要动。**方案选错后面全是泪。**这是我在这个项目里最深刻的教训。2. 文件读写的四个基本功2.1 open() 的完整参数解读Python 操作文件的入口是open()函数但这个函数有几个参数很多人用了一年也只用了前两个。file_obj open(file.txt, r, encodingutf-8)第一个参数是文件路径绝对路径或相对路径都行。第二个参数是打开模式这是最容易混乱的地方。我整理了一份表格把常用的几个模式区分清楚模式含义文件不存在时文件存在时文件指针位置r只读报错不截断开头w只写自动创建清空后写入开头a追加自动创建不清空末尾rb / wb / ab二进制读写同上同上同上r读写报错不截断开头w读写自动创建清空后写入开头a读写追加自动创建不清空末尾这里有两个最容易踩的坑。第一r模式文件不存在直接抛出FileNotFoundError但w和a模式会自动创建很多人没意识到自己的代码走的是哪个模式结果在文件缺失时懵了半天。第二w模式会清空原文件内容。这个行为一旦触发不可撤销我见过有人写日志脚本时用了w而不是a程序一启动历史日志全没了连恢复工具都救不回来。第三个参数encoding就更关键了。Python 在 Linux/macOS 上默认编码是 UTF-8但在 Windows 上默认是 GBK。同一个文件在一台机器上读出来正常换台机器就乱码多半就是编码参数没显式指定。所以我的习惯是只要是文本文件永远显式写encodingutf-8不给它留任何搞事情的空间。2.2 with 语句与资源回收初学者最常见的写法是f open(data.txt, w, encodingutf-8) f.write(hello) f.close()这个写法本身没有错但它有个致命隐患如果f.write()这行抛异常了f.close()根本不会执行文件句柄就泄露了。程序短时间跑还没什么长时间运行的话句柄越积越多最后系统报“Too many open files”整个程序崩溃。正确做法是with语句with open(data.txt, w, encodingutf-8) as f: f.write(hello)with语句会在代码块执行完毕后自动关闭文件无论中间是否发生异常。这相当于给文件操作加了一个保险。而且with语句不止能管文件后面你接触网络连接、数据库会话时它照样适用。**凡是需要“用完就释放”的资源都优先用with。**这不是“推荐风格”而是必要习惯。2.3 编码问题字符编码决定“乱不乱”字符编码这个事理论能讲一天但你只需要记住一个关键点写入和读取必须用同一个编码规则否则必定乱码。最常见的编码组合是 UTF-8。UTF-8 是目前全宇宙通用的事实标准跨平台、跨语言都认它。但在 Windows 下写代码经常遇到文件是 GBK 编码的情况比如很多老旧的日志文件、从某些国产软件里导出的文本、Excel 另存为的某些 CSV。读取这类文件时如果还指定utf-8Python 会直接报UnicodeDecodeError而不是乱码给你看。更隐蔽的是UTF-8 还有一种带 BOM 的变体叫utf-8-sig。有些 Windows 软件尤其是记事本保存 UTF-8 文件时会自动加上 BOM 头读文件时如果不指定utf-8-sig你会看到第一行开头莫名其妙多了一个不可见字符“\ufeff”。用utf-8-sig读取Python 会自动剥掉这个 BOM而且 UTF-8 的普通文件用utf-8-sig读也不会有副作用。所以遇到 BOM 问题时直接把编码换成utf-8-sig就完事。我在解析 WPS 导出的 CSV 时就遇到过类似的问题——文件本身就是 GBK 编码用 UTF-8 去读直接报错。后来统一用encodinggbk或者更省事的“先试 UTF-8失败再换 GBK”的策略才解决。那个常见的热搜词“wps表格在导出数据到文件时发生错误!(0x00000008)”其实就是导出过程中碰到了文件被占用、编码不兼容或者磁盘写入失败这类问题后面我会专门开一节展开说。2.4 路径处理相对路径与绝对路径的坑程序里写文件路径最忌讳的就是“随手硬编码一个绝对路径”。比如C:\Users\我的电脑\Desktop\data.txt这条路径换一台电脑几乎必废。更推荐的方式是使用相对路径让文件相对于程序脚本所在位置来定位。但相对路径也有坑这个“相对”到底是相对哪个目录很多人以为相对路径就是相对于当前运行的 .py 文件所在的目录但实际 Python 解释器在处理相对路径时是相对于“当前工作目录CWD”的。也就是说如果你在/home/user/project目录下运行python script/data.py程序里写的open(output/data.txt)会去找/home/user/project/output/data.txt而不是/home/user/project/data/output/data.txt。这个差异在调试时最容易糊弄人IDE 里跑得好好的一换命令行就找不到文件。解决这个问题有两个方向一是主动获取脚本所在目录再拼出完整路径二是直接用pathlib模块。pathlib是 Python 3.4 起引入的面向对象路径库我现在写所有文件相关代码都用它因为代码可读性提升非常明显from pathlib import Path base_dir Path(__file__).resolve().parent data_path base_dir / data / records.json data_path.parent.mkdir(parentsTrue, exist_okTrue) with open(data_path, w, encodingutf-8) as f: f.write(hello)Path(__file__).resolve().parent拿到的是“脚本文件所在的绝对目录”在此基础上拼路径不管你从哪个工作目录启动程序都能准确找到文件。还有一个细节mkdir(parentsTrue, exist_okTrue)会在目录不存在时自动创建多级目录这在写文件前规避“目录不存在”报错非常好用。顺带提一个热词“java 文件相关的操作”。Java 里对应的路径处理思路其实和 pathlib 很像——Java 用Paths.get()和Path对象来处理路径同样推荐基于“当前类所在位置”来定位资源文件而不是依赖工作目录。路径问题不分语言思路是共通的。3. 结构化数据的持久化方案3.1 纯文本最快但最弱的方案把数据写成纯文本是最朴素的方式。每行一个记录字段之间用逗号、竖线或者 Tab 分隔读取时逐行解析。缺点也明显结构一复杂就完蛋。你存“张三|2024-03-15|买了白菜花了3块5”这种规则数据还行如果一条记录里还有个“备注”字段而且备注里包含了竖线字符解析必炸。纯文本真正适合的场景是日志文件、简单的标记信息比如程序上次更新的时间戳、一次性导入导出的中间格式。它最大的优势是“谁都能读”——你用记事本打开就能看到内容出问题也好排查。但你要是准备把它当数据库用趁早打住。还有一个细节值得提纯文本写入时如果本身就存在同一路径下的文件w模式覆盖是直接截断的属于破坏性操作。为了保险我写关键数据时会先写一个临时文件比如data.txt.tmp写完确认无误后再用os.replace()原子替换原文件。这样即使程序在写入过程中崩溃原文件也不会被写坏一半。这个方法每次提都值得因为我真的靠它救回过一次配置数据。3.2 JSON跨语言数据交换的标准JSON 是我认为最值得优先掌握的持久化格式因为它的数据结构与 Python 的dict和list几乎是一一对应的。存的时候把一个字典json.dumps()成字符串读的时候json.loads()还原成字典逻辑非常顺畅。但这里有三个容易犯的错我一个一个说。第一中文乱码。默认情况下json.dumps()会把中文转成\uXXXX这种 Unicode 转义序列存进文件的是天书一样的东西。解决办法是在dumps()时指定ensure_asciiFalseimport json data {name: 张三, age: 28} with open(data.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)加了indent2之后文件里会按层级缩进用记事本打开也不会是挤成一团的一长串排查起来容易非常多。第二读文件时容易忘记处理“文件不存在”的情况。程序第一次启动时数据文件还没生成直接json.load()必然报FileNotFoundError。正确姿势是先判断文件是否存在不存在就返回一个默认的空结构from pathlib import Path import json data_path Path(data.json) def load_data(): if data_path.exists(): with open(data_path, encodingutf-8) as f: return json.load(f) return {items: []}第三写入的数据里如果有自定义类型的对象比如datetimejson.dump会直接报TypeError: Object of type datetime is not JSON serializable。解决办法是定义一个默认的序列化函数或者在存入之前先把数据转成字符串。我个人的习惯是在load_data/save_data两个函数里统一处理转换逻辑业务代码里不直接碰 JSON这个分层能让整个项目整洁很多。3.3 CSV表格数据的通用选择如果数据结构是规规矩矩的二维表CSV 是比 JSON 更合适的选择。它最大的优势是Excel、WPS、R、MATLAB 全都直接打开不需要任何转换。Python 标准库的csv模块用起来很简单但有个版本差异值得留意import csv # 写入 CSV with open(records.csv, w, encodingutf-8, newline) as f: writer csv.writer(f) writer.writerow([date, amount, note]) writer.writerow([2024-03-15, 35.0, 买菜]) # 读取 CSV with open(records.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: print(row[date], row[amount], row[note])newline这个参数是为了防止写入时出现多余的空行——在 Windows 上如果漏了它写出的 CSV 每隔一行就会多一个空行这个坑踩过的人都知道有多烦。用DictReader时表头会自动变成字典的 key读取时直接用列名取数据比row[0]、row[1]这种索引式取数清晰多了。另外还有个小坑Excel 打开 CSV 时默认按 GBK 编码解析如果你的 CSV 是 UTF-8 编码且不含 BOM直接在 Excel 里双击打开就会乱码。要让 Excel 正确识别要么存成utf-8-sig编码要么让用户通过“数据→自文本导入”指定编码。我用脚本导出一份给同事用的报表时就遇到过这种“程序里看完全正常Excel 打开全是乱码”的情况最后加了 BOM 头完美解决。3.4 SQLite单文件数据库进阶首选当数据量变大、查询变复杂文件方案就开始吃力了。比如你有十万条销售记录想统计“3 月份销售额超过 5000 的客户有哪些”如果用 JSON你得把整个文件加载进内存再逐条遍历如果数据文件 200MB光加载就够受的。这时候 SQLite 就该出场了。它是一个嵌入式关系型数据库整个数据库就是一个单独的 .db 文件不需要安装服务器不用配置端口直接用 Python 内置的sqlite3模块就能操作。它和“文件操作”一点都不冲突——本质上还是在读写文件只不过文件格式是结构化的数据库文件。import sqlite3 conn sqlite3.connect(app.db) cursor conn.cursor() cursor.execute(CREATE TABLE IF NOT EXISTS expenses (id INTEGER PRIMARY KEY, date TEXT, amount REAL, note TEXT)) cursor.execute(INSERT INTO expenses (date, amount, note) VALUES (?, ?, ?), (2024-03-15, 35.0, 买菜)) conn.commit() conn.close()SQLite 有几条必须遵守的铁律。第一写入之后必须commit()否则数据只在内存里程序一退出全没了。第二用完要close()释放连接不然文件会被锁住。第三SQL 语句里插入用户输入的值绝对不要用字符串拼接而是用?占位符传参数这样能防 SQL 注入。什么时候选 SQLite 而不是 JSON核心判断标准是数据的操作模式如果你是“一次性全部读取简单展示”的模式JSON 足够当你需要“频繁查询某几条、随时增删改、数据量级到千万行”SQLite 完胜。而且 SQLite 支持事务一批操作要么全部成功、要么全部回滚数据一致性比手撸文件可靠太多。我做本地工具时只要数据量可能超过几千条就会直接上 SQLite真正省心。4. 实操不到50行代码写一个带“记忆”的记账本4.1 需求拆解与数据结构设计既然要讲持久化单纯的语法演示没意义我直接带大家写一个能实际用的“命令行记账本”功能不复杂能添加一笔开销能查看所有记录能按日期筛选能统计总金额。程序退出后再启动所有记录都还在。数据结构很简单就是一个字典{items: [ {date: 2024-03-15, amount: 35.0, note: 买菜}, {date: 2024-03-16, amount: 18.5, note: 地铁充值} ]}选择 JSON 的原因是这个工具的数据量不大一个人一天记几笔一年也就上千条JSON 完全扛得住。而且 JSON 文件里字段结构改起来方便以后想加个“分类”字段只需要在新数据里多写一个键旧数据读出来没有这个键也不影响。4.2 核心代码加载、写入、追加、删除先把完整代码贴出来再逐段解释import json from pathlib import Path DATA_FILE Path(__file__).resolve().parent / records.json def load_data(): if DATA_FILE.exists(): with open(DATA_FILE, encodingutf-8) as f: data json.load(f) if items not in data: data[items] [] return data return {items: []} def save_data(data): temp DATA_FILE.with_suffix(.json.tmp) with open(temp, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) temp.replace(DATA_FILE) def add_item(data, date, amount, note): data[items].append({date: date, amount: amount, note: note}) def list_items(data, dateNone): items data[items] if date: items [item for item in items if item[date] date] total sum(item[amount] for item in items) for item in items: print(f{item[date]} {item[amount]:8.2f} {item[note]}) print(f总计: {total:.2f}) if __name__ __main__: data load_data() while True: op input(请输入操作(add/list/total/exit): ).strip() if op add: date input(日期(YYYY-MM-DD): ) amount float(input(金额: )) note input(备注: ) add_item(data, date, amount, note) save_data(data) elif op list: d input(筛选日期(直接回车显示所有): ).strip() list_items(data, d if d else None) elif op total: list_items(data) elif op exit: save_data(data) break这里有几个值得细看的设计。save_data函数里我先写临时文件再通过temp.replace(DATA_FILE)原子替换。这样做的道理是如果程序在写records.json的过程中突然断电原来的文件已经被截断成半个 JSON下次load_data一定报错。而用临时文件 原子替换要么替换成功要么原文件完整保留数据永远不处于“写了一半”的状态。这个模式做任何文件持久化都建议用。load_data里还对旧数据做了一层兜底如果 JSON 文件存在但里面没有items键比如你手动编辑过文件就自动补上空列表避免后续data[items].append()报KeyError。4.3 一个隐藏很深的bug覆盖写入与追加写入这个记账本代码里有个很容易犯的经典错误我必须单独拎出来讲。假设你按直觉把save_data写成这样def save_data(data): with open(DATA_FILE, w, encodingutf-8) as f: json.dump(data, f)看着没问题对不对但如果用户连续添加两笔记录第二笔操作结束后data里其实已经包含了第一笔的数据所以save_data整体写入是没问题的。真正的问题会出现在你换一种实现时——比如每次add_item不是直接修改data而是重新读文件、追加一行、再写回。这个时候如果打开模式用了w就会覆盖掉之前的内容用了a则可能把 JSON 追加成了非法格式两个字典连在一起中间没有逗号、没有方括号包裹导致整个文件json.load失败。很多人在“覆盖写入”和“追加写入”之间反复横跳就是因为没想清楚**数据是“全量保存”还是“增量保存”。**记账本这种场景数据量小、逻辑简单全量保存更安全日志这种场景数据量大、只增不改追加写入更合适。两种模式的取舍标准就是你的数据需要修改历史记录吗需要随机删除和更新吗需要的话别用追加老老实实全量保存。5. 系统层面的文件操作与常见坑5.1 磁盘与目录的排查命令df 和 du程序跑着跑着突然写不进文件了第一反应不应该是看代码而是查磁盘是不是满了。在 Linux 和 macOS 系统里df和du这两个命令就是干这个的# 查看各挂载点的磁盘使用情况 df -h # 查看当前目录下各子目录占用的空间 du -sh *df -h输出里关注Use%这一列如果某个分区到了 100%写文件必然失败。而且大文件写入失败有时候不是立即报错而是在缓存同步时失败这种问题通过df -h一眼就能定位。du -sh *则适合排查“目录里什么东西一直在涨”的问题——比如日志文件自动膨胀把磁盘吃满了跑一下这个命令就能揪出元凶。在 Windows 上对应的操作是打开“此电脑”看磁盘容量条或者用 PowerShell 的Get-PSDrive C查看剩余空间。还有一个思路写文件前用代码显式判断剩余空间比如 Python 的shutil.disk_usage()函数import shutil usage shutil.disk_usage(/) print(f剩余空间: {usage.free / 1024**3:.2f} GB)在保存关键数据之前检查这一点能拦截大部分“磁盘满了”导致的数据写入失败。5.2 Windows 下最常见的权限问题很少有人在学编程的第一天就碰到“你需要 trusteinstaller 提供的权限才能对此文件进行操作”直到某天他们想改 C 盘 Windows 目录下的某个系统文件甚至只是想删掉一个“顽固”的旧文件夹时这个提示就冒出来了。TrustedInstaller 是 Windows 系统服务的一部分专门负责 Windows 更新等受保护文件的完整性。普通管理员权限都不能改这些文件原因很简单——系统防止你手滑或者被恶意软件破坏关键文件。解决方式一般有两种一是临时修改文件的所有者和管理权限也就是网上各个教程里“夺取所有权”那一套二是换个思路不去动系统文件而是把程序的数据和日志写到用户目录下。第二个思路其实是更健康的方案。Windows 下安全可靠的文件写入目录是%APPDATA%即C:\Users\用户名\AppData\Roaming或者用户文档目录。在 Python 里可以这样获取from pathlib import Path import os appdata Path(os.environ.get(APPDATA, Path.home())) data_dir appdata / MyApp data_dir.mkdir(parentsTrue, exist_okTrue)把数据写到用户目录不仅躲开了权限问题还能做到多用户隔离——每个用户的数据各自独立互不干扰。这也是正经软件的标准做法。所以遇到权限报错我一般分两步走先确认这个文件是不是真的必须改如果必须改再用最高权限去操作如果不是非它不可果断把数据换到用户目录一劳永逸。5.3 文件被占用、打开方式错误、WPS导出失败文件被其他程序占用导致操作失败这个场景在 Windows 上出现频率很高。最常见的就是你打开了一个 Excel 表格结果程序去写同一个文件弹出“文件正由另一进程使用”。排查方法打开任务管理器看相关进程或者用 PowerShell 工具查找句柄。但更省事的办法是在代码里做好“占用检测”——写文件时先用只读方式尝试打开如果失败就往文件名后面加时间戳写成新文件不跟用户硬碰硬。还有一个很常见的 Windows 系统问题“win10 文件右键打开方式提示没有与之关联应用来执行该操作”。这个看着和编程无关但程序员日常也会遇到。它本质上是因为文件扩展名对应的默认程序被清掉了或者注册表关联损坏。解决办法通常是用系统的“默认应用”设置重新指定一次关联程序或者用“打开方式→选择其他应用”手动指定。不过这个操作治标不治本如果同一个扩展名反复丢失关联多半是安装了某些软件后修改了默认关联去软件设置里把“关联文件类型”关掉就好。再来说热词里的“WPS 表格在导出数据到文件时发生错误 (0x80000008)”。这个报错码其实是 I/O 错误的通用表示。我遇到过的真实原因有几个文件被 Excel 或 WPS 自身占用了先关掉正在预览的表格再导出、目标磁盘是 U 盘且写保护打开了、磁盘剩余空间不足导致临时文件写不进去、原文件的编码格式和导出插件不兼容。排查顺序建议先检查目标文件是否被打开换个文件名导出试试再检查磁盘空间最后看是不是文件属性设了“只读”。很多时候就是“文件在别处开着”这一个原因换个名字就好。5.4 Java 的文件操作与 Python 的一个对比热词里有“Java 文件相关的操作”这里简单对比一下因为理解两种语言的文件操作差异能帮你把“文件操作”这件事的本质看得更透。Java 里最常用的是java.nio.file.Files和Pathsimport java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.charset.StandardCharsets; import java.util.List; Path path Paths.get(data.txt); ListString lines Files.readAllLines(path, StandardCharsets.UTF_8); Files.write(path, lines, StandardCharsets.UTF_8);是不是发现和 Python 的pathlib思路很像Paths.get()对应Path()Files.readAllLines()对应open().readlines()。核心差异就两个第一Java 的标准库更“重”文件操作要处理受检异常IOExceptionPython 则用运行时异常处理第二Java 的Files.write()默认也是覆盖写入想追加也一样需要指定StandardOpenOption.APPEND。无论用哪种语言文件操作的底层逻辑都是一样的——无非是“打开文件、移动指针、读/写、关闭”。语言不同只是 API 不同理解了本质换语言只是换语法不用重新学概念。6. 常见问题速查表文件操作与持久化的低频高坑问题我把这些年实际踩过的、以及在社区里高频出现的问题整理成了表格按“问题→原因→解决”的方式列清楚方便大家当成速查手册报错/现象常见原因解决方法FileNotFoundError文件不存在且用了 r 或 r 模式检查路径是否正确改用 w/a 模式或先创建文件PermissionError文件被占用、权限不足、只读属性关闭占用程序检查写保护换用户目录写入UnicodeDecodeError / UnicodeEncodeError编码参数不匹配读和写都显式指定 encodingutf-8旧文件试试 gbk 或 utf-8-sig中文乱码写入用 UTF-8、读取用 GBK或 Excel 打开 UTF-8 无 BOM 文件统一编码CSV 需要 Excel 兼容时用 utf-8-sigJSONDecodeError文件内容被截断、或被人为改坏用临时文件原子替换写入load 前增加异常处理考虑用 SQLite 替代csv 文件每行之间有空行写入时没指定 newlinewith open(a.csv, w, newline)数据一重启就消失只写在了内存没调用 save 或 commit检查是否执行了写入SQLite 检查 commit文件写入后确认 close第一次运行时报文件不存在还没有生成数据文件load 时先判断 exists()不存在返回空默认结构文件里有 \ufeff 前缀UTF-8 BOM 头读文件时用 encodingutf-8-sig磁盘满导致写入失败日志或缓存文件无限膨胀用 df -h 检查给程序增加磁盘空间检测日志做轮转排查文件类问题的顺序我个人总结了一个口诀先看权限再看空间三看编码最后看代码逻辑。权限和空间的问题往往在代码之外很多人报错就先埋头改代码改半天发现是磁盘满了非常浪费时间。编码问题则可以通过在读写参数里显式指定编码来快速排除。把代码逻辑放最后是因为代码问题往往通过报错信息直接就能定位反而是环境问题最隐蔽。最后再分享一个我踩过最值得说的一次坑前年我做一个公司内部的数据采集工具日志文件是每天一个按日期命名。当时图省事打开模式用了w想着“反正每天一个文件覆盖也无所谓”。结果有一天运维同事告诉我连续几天的日志全没了。我排查了半天最后发现是系统时钟被 NTP 同步跳了日期进程跨天的时候新文件直接覆盖了当天已经写了几个小时的旧日志。从那次以后我的所有日志类文件一律用追加模式a并强制加上了“如果文件已存在且非空则换名写成带时间戳的新文件”的逻辑。文件操作和数据持久化表面上就是“open 一下、write 一下、close 一下”这么简单但真正让你写出稳定可靠代码的是那些边界情况文件不存在怎么办、被人占用了怎么办、写到一半断电怎么办、编码不匹配怎么办、磁盘满了怎么办。把这些情况在脑子里过一遍你的程序才真正配得上“拥有记忆”这四个字。希望这一节的内容能帮你把这些问题提前挡住少走我当年走过的弯路。
阅读完成 · 觉得有帮助?
咨询建站