游戏开发后端【免费下载链接】kbengineA MMOG engine of server.项目地址https://gitcode.com/gh_mirrors/kb/kbengine点击查看免费下载导读shelve是 Python 标准库中一个将对象序列化后持久化到磁盘的模块它提供一种持久化的字典persistent dictionary让开发者可以用读写普通 dict 的方式保存任意 Python 对象。本文以 KBEngine 仓库内嵌的 CPython 3.7.3 发行版Lib/shelve.py 及其官方文档 Doc/library/shelve.rst为主体完整讲解shelve.open的参数语义、三个核心类、可变对象写入陷阱、同步与关闭机制并结合源码与测试用例Lib/test/test_shelve.py深入剖析其底层实现帮助读者在游戏服务器脚本或其他 Python 场景中安全、高效地使用 shelf 存储。什么是 shelf与 dbm 的本质区别shelve模块在文档中开宗明义shelf 是一个持久化的、类字典对象。它与dbm数据库的关键差异在于dbm键和值都只能是字符串或字节串shelf键是普通字符串值可以是任意 Python 对象——包括大多数类实例、递归数据类型、包含大量共享子对象的对象只要是pickle模块能够处理的对象即可。原因在于 shelf 的底层存储机制值先经pickle序列化为字节串再交给 dbm 风格的数据库保存读取时再反序列化回 Python 对象。模块源码开头的 docstring 与实现 Lib/shelve.py 完全一致地说明了这一点其核心序列化依赖正是from pickle import Pickler, Unpickler与from io import BytesIO。在 KBEngine 项目中该模块位于服务器进程内嵌的 Python 解释器环境中。引擎通过 src/lib/pyscript/script.cpp 中的Script::install()调用Py_SetPythonHome、Py_SetPath、Py_Initialize等启动内置解释器其版本由 Include/patchlevel.h 定义为PY_VERSION 3.7.3。因此本仓库中 shelve 的 API 行为以 Python 3.7.3 为准例如open()不暴露keyencoding参数但Shelf类自 3.2 起支持该参数。核心函数shelve.open(filename, flagc, protocolNone, writebackFalse)import shelve db shelve.open(my_shelf)shelve.open用于打开一个持久化字典其签名与文档一致参数默认值含义filename必填底层数据库的基础文件名。副作用是底层库可能为文件追加扩展名甚至生成多个文件flagc与dbm.open的flag参数语义相同r只读、w读写、c读写且必要时创建、n总是新建空库protocolNone即 3序列化值所用的 pickle 协议版本默认使用版本 3 的 picklewritebackFalse是否开启写回缓存决定可变条目的修改能否被自动感知见下文陷阱一节源码层面open()只是DbfilenameShelf的便捷封装Lib/shelve.pydef open(filename, flagc, protocolNone, writebackFalse): return DbfilenameShelf(filename, flag, protocol, writeback)而DbfilenameShelf.__init__内部调用dbm.open(filename, flag)获得底层数据库对象再交给Shelf构造器Lib/shelve.py。关于flag的完整语义与dbm.whichdb的探测行为可对照同目录下的 Doc/library/dbm.rst。协议版本与默认值在Shelf.__init__中protocol为None时会被显式赋值为3Lib/shelve.pyif protocol is None: protocol 3 self._protocol protocol这一默认值也被测试用例验证test_default_protocol断言shelve.Shelf({})._protocol 3。协议版本的选择直接影响序列化字节流的大小与兼容性——版本 0 为文本格式ASCII版本 1 引入二进制格式版本 2 支持类与实例更高效的序列化版本 3 起支持bytes对象等新特性详见 Doc/library/pickle.rst。测试 Lib/test/test_shelve.py 中的test_ascii_file_shelf、test_binary_file_shelf、test_proto2_file_shelf分别以protocol0/1/2打开文件型 shelf 并读写元组数据验证各协议下的可用性。三个核心类Shelf、BsdDbShelf、DbfilenameShelfShelf(dict, protocolNone, writebackFalse, keyencodingutf-8)Shelf是 shelf 实现的基类是collections.abc.MutableMapping的子类将 pickle 后的值存入传入的dict 对象任意类字典对象中。参数dict底层类字典存储键必须是字节串protocolpickle 协议版本默认 3writeback为True时缓存所有访问过的条目并在sync()/close()时写回keyencoding写入底层 dict 前对键使用的编码默认utf-8。该参数自 Python 3.2 加入此前键固定按 UTF-8 编码3.4 起Shelf支持上下文管理器协议。源码中键的编解码发生在每个魔术方法里__contains__、__getitem__、__setitem__、__delitem__都通过key.encode(self.keyencoding)转换键而__iter__则用k.decode(self.keyencoding)还原Lib/shelve.py。测试test_keyencoding验证了 UTF-8 默认编码、可替换为latin-1以及选用ascii时对非 ASCII 键抛出UnicodeEncodeError的行为。BsdDbShelf(dict, protocolNone, writebackFalse, keyencodingutf-8)BsdDbShelf是Shelf的子类额外暴露first()、next()、previous()、last()与set_location()五个游标式遍历方法。这些方法仅存在于第三方bsddb模块如 pybsddb其他 dbm 后端没有对应物。使用它时传入的 dict 必须支持这些方法通常由bsddb.hashopen、bsddb.btopen或bsddb.rnopen打开。源码实现中每个方法都从底层 dict 取出原始字节经key.decode(self.keyencoding)还原键、Unpickler(f).load()还原值Lib/shelve.py。DbfilenameShelf(filename, flagc, protocolNone, writebackFalse)DbfilenameShelf是Shelf的子类接受文件名而非 dict 对象内部用dbm.open(filename, flag)打开底层文件。默认情况下文件会被创建并同时以读写方式打开。它是shelve.open()直接返回的类型也是日常使用最多的一条路径。sync()与close()两个额外的字典方法shelf 对象支持字典的全部方法此外还有两个额外方法Shelf.sync()若以writebackTrue打开将缓存中所有条目写回同时清空缓存并在可行时同步底层持久化字典到磁盘。close()会自动调用它。Shelf.close()同步并关闭底层持久化字典。对已关闭的 shelf 执行操作会抛出ValueError。源码中sync()的实现很巧妙Lib/shelve.pydef sync(self): if self.writeback and self.cache: self.writeback False for key, entry in self.cache.items(): self[key] entry self.writeback True self.cache {} if hasattr(self.dict, sync): self.dict.sync()先临时关闭writeback标志再逐项写回是为了避免写回过程再次把条目塞回缓存写回完成后再清空缓存。而close()会先把self.dict替换为_ClosedDict标记对象——_ClosedDict的所有操作__iter__、__len__、__getitem__等统一抛出ValueError(invalid operation on closed shelf)Lib/shelve.py这就是关闭后操作失败的机制来源。测试test_close与test_with均验证了关闭后访问会抛ValueError。关键陷阱可变对象的写入语义与 writebackshelf 无法感知持久化字典中的可变条目何时被就地修改。默认情况下writebackFalse修改后的对象只有在重新赋值给 shelf 时才会写入磁盘。这一点无论文档还是源码都反复强调是使用 shelf 最常见的坑。经典示例完整保留自官方文档import shelve d shelve.open(filename) # 打开——底层库可能给文件名追加后缀 d[key] data # 在 key 处存储 data若 key 已存在则覆盖旧数据 data d[key] # 取回 key 处数据的一个副本不存在则抛 KeyError del d[key] # 删除 key 处数据不存在则抛 KeyError flag key in d # 判断 key 是否存在 klist list(d.keys()) # 列出所有现有 key很慢 # 因为 d 是以 writebackFalse 打开的请务必注意 d[xx] [0, 1, 2] # 这行符合预期但是…… d[xx].append(3) # *这行不生效* —— d[xx] 仍然等于 [0, 1, 2] # 以 writebackFalse 打开时你需要这样小心地写 temp d[xx] # 取出副本 temp.append(5) # 修改副本 d[xx] temp # 把副本重新存回去才能持久化 # 或者 d shelve.open(filename, writebackTrue) 会让你直接写 # d[xx].append(5) 也能生效但代价是占用更多内存、close() 更慢。 d.close() # 关闭源码层面的原因__getitem__从底层 dict 取出字节串后用Unpickler(BytesIO(...)).load()反序列化出一个全新的对象返回Lib/shelve.py。调用方对这个返回值的就地修改并不会反向影响底层存储中的字节串——除非重新d[key] temp触发__setitem__的Pickler(...).dump(value)重新序列化。测试test_mutable_entry精确复现了这一行为writebackFalse时s[key1]存入[1,2,3,4]后执行s[key1].append(5)再次读取仍为[1,2,3,4]writebackTrue时同样的代码读出来是[1,2,3,4,5]。开启 writeback 的代价writebackTrue时所有访问过的条目都会缓存在内存self.cache中并在sync()/close()时全部写回。由于 shelf 无法判断哪些条目可变、哪些被真正修改过只能缓存并回写所有访问过的条目因此若访问的条目很多缓存会消耗大量内存close()会变得很慢因为要写回所有被访问过的条目。测试test_writeback_also_writes_immediately还验证了一个容易被忽略的细节writebackTrue时d[key] value的赋值本身也会立即写入底层存储底层字节串已更新只是同时保留一份缓存用于后续感知修改。上下文管理器推荐的安全关闭方式官方文档明确告诫不要依赖 shelf 被自动关闭不需要时应显式调用close()或者把shelve.open用作上下文管理器with shelve.open(spam) as db: db[eggs] eggs上下文管理器支持自 Python 3.4 加入。实现上是Shelf定义了__enter__返回自身与__exit__调用self.close()Lib/shelve.py。即使不显式关闭Shelf的__del__也会尝试兜底关闭但解释器关闭期间close()可能抛错因此代码中通过try/except保证self.dict至少被替换为_ClosedDict或NoneLib/shelve.py。测试test_with验证了with块结束后再访问 shelf 会抛ValueError。安全警告不要从不可信来源加载 shelf因为 shelf 底层由pickle支撑从不可信来源加载 shelf 是不安全的。与直接使用pickle.load一样加载一个精心构造的 shelf 文件可能执行任意代码。官方文档以 warning 形式强调此点这一点在 Lib/shelve.py 的 docstring 中同样有体现。在 KBEngine 这类多进程、多用户可触及资源的服务器环境中尤其要注意只加载自己生成、可受信任的 shelf 文件。使用限制后端差异、体积与并发后端选择不确定shelf 使用哪个数据库包dbm.ndbm或dbm.gnu取决于当前环境有哪些接口可用。因此不要直接用dbm打开 shelf 文件——你无法预先确定文件对应的后端。推荐用dbm.whichdb(filename)探测格式其返回值如dbm.ndbm、dbm.gnu或空字符串/None见 Doc/library/dbm.rst。若环境中没有dbm.gnu/dbm.ndbm则会回退到慢但简单的dbm.dumb实现。受 dbm 自身限制如果底层使用 dbm存储对象的 pickle 表示应当相当小极少数情况下键冲突可能导致数据库拒绝更新。不支持并发读写shelf不支持对同一批对象的并发读写多个进程同时只读是安全的。当某个程序以写模式打开 shelf 时其他程序不应再以读或写模式打开它。解决方案是 Unix 文件锁但不同 Unix 版本差异很大且需要了解底层数据库实现。在 KBEngine 仓库中查看与验证模块源码kbe/src/lib/python/Lib/shelve.py ——open、Shelf、BsdDbShelf、DbfilenameShelf、sync、close的完整实现官方文档本主题主体kbe/src/lib/python/Doc/library/shelve.rst单元测试kbe/src/lib/python/Lib/test/test_shelve.py —— 覆盖关闭语义、各 pickle 协议、内存型 shelf、可变条目写回、键编码、上下文管理器等行为可用python -m test.test_shelve方式运行验证关联模块底层 dbm 接口见 Doc/library/dbm.rst序列化协议见 Doc/library/pickle.rst内嵌解释器KBEngine 服务器进程通过 src/lib/pyscript/script.cpp 初始化内置 Python版本为 Include/patchlevel.h 定义的 3.7.3shelve即位于该解释器自带的Lib标准库目录中。小结shelve以字典接口 pickle 序列化 dbm 存储三层结构提供了轻量级的对象持久化方案适合存放配置对象、缓存数据等中小体量、无并发写需求的场景。使用时的三个要点牢记读操作返回副本对可变对象要么显式写回、要么开启writeback并接受内存与关闭开销显式close()或使用with只加载可信来源的 shelf 文件。理解了 Lib/shelve.py 的源码与 test_shelve.py 的测试用例就能准确预判 shelf 在各种边界条件下的行为。赞分享游戏开发后端【免费下载链接】kbengineA MMOG engine of server.项目地址https://gitcode.com/gh_mirrors/kb/kbengine点击查看免费下载相关推荐KBEngine 内嵌 Python 的 posix/os 模块POSIX 系统调用、大文件支持与 environ 环境字典解析KBEngine 内嵌 Python 的 posix/os 模块POSIX 系统调用、大文件支持与 environ 环境字典解析 KBEngine 的服务器端游戏开发后端KBEngine 内嵌 Python 的 compileall 模块批量字节码编译的原理、参数与实战用法KBEngine 内嵌 Python 的 compileall 模块批量字节码编译的原理、参数与实战用法 KBEngine 服务端内嵌了 CPython 解释游戏开发后端KBEngine 内嵌 Python 3.7 的 keyword 模块关键字检测、kwlist 全表与源码生成机制解析KBEngine 内嵌 Python 3.7 的 keyword 模块关键字检测、kwlist 全表与源码生成机制解析 导读 keyword 是 Python游戏开发后端上一篇public-image-mirror性能监控关键指标收集下一篇Loki 标签提取设计Promtail 日志处理 Pipeline 从设计文档到源码实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考