返回介绍

14.13 案例分析:在数据库转换工具中使用生成器

发布于 2024-02-05 21:59:47 字数 2566 浏览 0 评论 0 收藏 0

几年前,我在 BIREME 工作,这是 PAHO/WHO(Pan-American Health Organization/World Health Organization,泛美卫生组织 / 世界卫生组织)在圣保罗运营的一家数字图书馆。 BIREME 制作的众多书目数据集中包含 LILACS(Latin American and Caribbean Health Sciences index,拉美和加勒比地区健康科学索引)和 SciELO(Scientific Electronic Library Online,电子科学在线图书馆),这两个数据库完整索引了这一地区发布的科学和技术作品。

从 20 世纪 80 年代后期开始,管理 LILACS 的数据库系统是 CDS/ISIS。这是 UNESCO 开发的非关系型文档数据库,后来为了在 GNU/Linux 服务器上运行,BIREME 使用 C 语言重写了。我的工作之一是探索替代方案,把 LILACS 移植到现代的开源文档数据库(最终还要移植大得多的 SciELO),例如 CouchDB 或 MongoDB。

在探索的过程中,我编写了一个 Python 脚本——isis2json.py,把 CDS/ISIS 文件转换成适合导入 CouchDB 或 MongoDB 的 JSON 文件。起初,这个脚本读取文件的是 CDS/ISIS 导出的 ISO-2709 格式。读写过程必须采用渐进方式,因为完整的数据集比主内存大得多。解决方法很简单:主 for 循环每次迭代时从 .iso 文件中读取一个记录,转换后将其写入 .json 文件。

然而,在实际操作中有必要让 isis2json.py 支持 CDS/ISIS 的另一种数据格式——BIREME 在生产环境中使用的二进制 .mst 文件,避免导出为 ISO-2709 格式时消耗过多资源。

现在我遇到一个问题:用来读取 ISO-2709 和 .mst 文件的库提供的 API 差别很大。而输出 JSON 格式的循环已经很复杂了,因为这个脚本要接受多个命令行选项,每次输出时调整记录的结构。在同一个 for 循环中使用两个不同的 API,同时还要生成 JSON,这样太难以管理了。

解决方法是隔离读取逻辑,写进两个生成器函数中:一个函数支持一种输入格式。最终,我把 isis2json.py 脚本分成了四个函数。使用 Python 2 编写的主脚本如示例 A-5,带依赖的完整源码在 GitHub 中的 fluentpython/isis2json 仓库里。

下面概览这个脚本的结构。

main

main 函数使用 argparse 模块读取命令行选项,用于配置输出记录的结构。根据输入文件的扩展名,main 函数会选择一个合适的生成器函数,逐个读取数据,然后产出记录。

iter_iso_records

这个生成器函数用于读取 .iso 文件(假设是 ISO-2709 格式),有两个参数:一个是文件名;另一个是 isis_json_type,即一个与记录结构有关的选项。在这个函数的 for 循环中,每次迭代读取一个记录,然后创建一个空字典,把数据填充进字段之后产出字典。

iter_mst_records

这也是一个生成器函数,用于读取 .mst 文件。15 阅读 isis2json.py 脚本的源码后你会发现,这个函数没有 iter_iso_records 函数简单,不过接口和整体结构是相同的:参数是文件名和 isis_json_type,for 循环每次迭代时构建并产出一个字典,表示一个记录。

15用来读取复杂的 .mst 二进制文件的库其实是用 Java 编写的,因此只有使用 Jython 解释器 2.5 或以上版本执行 isis2json.py 脚本才能使用这个功能。详情参见仓库里的 README.rst 文件。因为依赖在需要使用的生成器函数中导入,所以即便只有一个外部依赖可用,这个脚本仍能运行。

write_json

这个函数把记录输出为 JSON 格式,而且一次输出一个记录。它的参数很多,其中第一个参数(input_gen)是对某个生成器函数的引用:iter_iso_records 或 iter_mst_records。write_json 函数的主 for 循环迭代 input_gen 引用的生成器产出的字典,根据命令行选项设定的方式处理,然后把 JSON 格式的记录附加到输出文件里。

我利用生成器函数解耦了读逻辑和写逻辑。当然,解耦二者最简单的方式是,把所有记录读进内存,然后写入硬盘。可是这样并不可行,因为数据集很大。而使用生成器的话,可以交叉读写,因此这个脚本可以处理任意大小的文件。

现在,如果 isis2json.py 脚本需要再支持一种输入格式,比如说美国国会图书馆用于表示 ISO-2709 格式数据的 MARCXML 文档格式,只需再添加一个生成器函数,实现读逻辑,而复杂的 write_json 函数无需任何改动。

这不是什么尖端科技,可是通过这个实例我们看到了生成器的灵活性。使用生成器处理数据库时,我们把记录看成数据流,这样消耗的内存量最低,而且不管数据有多大都能处理。只要管理着大型数据集,都有可能在实践中找到机会使用生成器。

下一节讨论暂时要跳过的一个生成器特性。为什么要跳过呢?原因如下。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文