python模型管道函数 python管道阻塞( 五 )


我们第一步要把数据库的模型建立起来 。为了测试目的,我们使用一个最简单的数据库模型 , 它只包含两个节点和一条边 , 如下所示:
按照TDD 的原则,首先编写测试:
与原文一样,我们把数据库管理接口命名为Dagoba。目前,能够想到的最简单的测试是确认节点和边是否已经添加到数据库中:
assert_item 是一个辅助方法,用于检查字典是否包含预期的字段 。相信大家都能想到该如何实现,这里就不再列出了,读者可参考 Github 上的完整源码 。
现在,测试是失败的 。用最简单的办法实现数据库:
需要注意的是,不管添加节点还是查询,程序都使用了拷贝后的数据副本,而不是直接使用原始数据 。为什么要这样做?因为字典是可变的 , 用户可以在任何时候修改其中的内容,如果数据库不知道数据已经变化,就很容易发生难以追踪的一致性问题,最糟糕的情况下会使得数据内容彻底混乱 。
拷贝数据可以避免上述问题,代价则是需要占用更多内存和处理时间 。对于数据库来说 , 通常查询次数要远远多于修改,所以这个代价是可以接受的 。
现在测试应该正常通过了 。为了让它更加完善,我们可以再测试一些边缘情况,看看数据库能否正确处理异常数据,比如:
例如,如果用户尝试添加重复主键,我们预期应抛出ValueError 异常 。因此编写测试如下:
为了满足以上测试 , 代码需要稍作修改 。特别是按照id 查找主键是个常用操作,通过遍历的方法效率太低了,最好是能够通过主键直接访问 。因此在数据库中再增加一个字典:
完整代码请参考Github 仓库 。
在上个步骤,我们在初始化数据库时为节点明确指定了主键 。按照数据库设计的一般原则 , 主键最好是不具有业务含义的代理主键( Surrogate key ) , 用户不应该关心它具体的值是什么,因此让数据库去管理主键通常是更为合理的 。当然,在部分场景下——比如导入外部数据——明确指定主键仍然是有用的 。
为了同时支持这些要求 , 我们这样约定:字段_id 表示节点的主键 , 如果用户指定了该字段,则使用用户设置的值(当然 , 用户有责任保证它们不会重复);否则,由数据库自动为它分配一个主键 。
如果主键是数据库生成的,事先无法预知它的值是什么,而边( edge )必须指定它所指向的节点,因此必须在主键生成后才能添加 。由于这个原因,在动态生成主键的情况下,数据库的初始化会略微复杂一些 。还是先写一个测试:
为支持此功能,我们在数据库中添加一个内部字段_next_id 用于生成主键,并让 add_node 方法返回新生成的主键:
接下来,再确认一下边是否可以正常访问:
运行测试,一切正常 。这个步骤很轻松地完成了 , 不过两个测试( DbModelTest 和 PrimaryKeyTest )出现了一些重复代码,比如 get_item。我们可以把这些公用代码提取出来 。由于 get_item 内部调用了 TestCase.assertXXX 等方法,看起来应该使用继承,但从 TestCase 派生基类容易引起一些潜在的问题,所以我转而使用另一个技巧 Mixin :
实现数据库模型之后,接下来就要考虑如何查询它了 。
在设计查询时要考虑几个问题 。对于图的访问来说,几乎总是由某个节点(或符合条件的某一类节点)开始,从与它相邻的边跳转到其他节点,依次类推 。所以链式调用对查询来说是一种很自然的风格 。举例来说,要知道 Tom 的孙子养了几只猫,可以使用类似这样的查询:

推荐阅读