shelve天然支持字典接口、自动序列化和读多写少场景的并发安全,避免JSON的全量加载、原子写入缺失及多进程写损坏问题;其底层基于dbm按key粒度操作,无需手动dump/load。

直接用 shelve,别自己封装 JSON 文件读写——它天然支持字典接口 + 自动序列化 + 并发安全(读多写少场景),且比手写 pickle 更省心。
为什么不用 json.dump + json.load 模拟持久化字典?
看似简单,但实际会踩三个坑:
- 每次
load都得全量读入内存,大字典(>10MB)启动慢、占内存 - 写入必须先
load→ 修改 →dump,中间出错(如断电)直接丢数据 - 没有原子写入保障,多进程同时写一个 JSON 文件大概率损坏文件
而 shelve 底层基于 dbm(如 bsddb 或 ndbm),按 key 粒度读写,不加载全量数据,也支持写时加锁。
如何正确打开和使用 shelve.open?
关键在 writeback 参数和文件模式:
- 默认
shelve.open("data.db")是只读模式,要写必须显式加flag="c"(create/read/write) -
writeback=False(默认):修改嵌套结构(如d["a"].append(1))不会自动回写,得手动赋值d["a"] = d["a"] -
writeback=True:所有修改自动缓存并落盘,但会吃内存、拖慢关闭速度,且不支持多进程共享 - 推荐做法:
d = shelve.open("cache.db", flag="c", writeback=False),改嵌套值时显式重赋值
示例:
d = shelve.open("config.db", flag="c")
d["host"] = "localhost"
d["ports"] = [80, 443]
# 修改列表需重赋值
ports = d["ports"]
ports.append(8080)
d["ports"] = ports # 必须这句
d.close()
哪些值类型不能存进 shelve?
shelve 本质调用 pickle,所以限制和 pickle 一致:
- 支持:
str、int、list、dict、tuple、bytes、None - 不支持:
lambda、function、class实例(除非定义了__reduce__)、threading.Lock等不可序列化对象 - 特别注意:
datetime对象能存,但反序列化后是datetime.datetime实例,不是字符串——这点和 JSON 不同
多进程访问时怎么避免损坏?
shelve 本身不保证多进程写安全。常见错误是多个进程同时 open(..., flag="c") 写同一个文件,导致 DB 文件头冲突。
- 只读场景:多个进程
open(..., flag="r")安全 - 读多写少:用单个长期运行的守护进程管理
shelve,其他进程通过 IPC(如multiprocessing.Queue)通信 - 真要多进程写:换
sqlite3(建一张key TEXT PRIMARY KEY, value BLOB表),或用lmdb(更轻量、真正支持多进程只读+单写)
最易忽略的一点:shelve.open 返回的对象不是线程安全的,即使在单进程多线程下,也要自己加 threading.Lock 控制写操作。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











