- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
14.13 案例分析:在数据库转换工具中使用生成器
几年前,我在 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论