Python DB-API

作者:Andrew M. Kuchling

许多人使用 Python 是因为,像其他脚本语言一样,它是一种可移植的、平台独立的通用语言,可以执行与数据库供应商提供的以数据库为中心的专有 4GL 工具相同的任务。像 4GL 工具一样,Python 让您能够轻松编写访问、显示和更新数据库中信息的程序。与许多 4GL 不同,Python 还为您提供了各种其他功能,例如解析 HTML、建立套接字连接和加密数据。

Python DB-API 的可能应用包括

  • 许多网站动态构建页面以显示用户请求的信息,例如从目录中提供的产品或从图书馆中可用的文档中进行选择。这样做需要 CGI 脚本,这些脚本可以从数据库中提取所需信息并将其渲染为 HTML。

  • 一个 Intranet 应用程序可以使用 Tkinter 模块和 DB-API 来为浏览本地数据库(例如应收账款或客户列表)提供图形用户界面。

  • Python 程序可用于通过计算数据的统计属性来分析数据。

  • Python 程序可以为修改数据库的程序形成测试框架,以验证是否维护了特定的完整性约束。

现在有很多商业和免费数据库可用,它们中的大多数都提供结构化查询语言 (SQL) 用于检索和添加信息(请参阅“资源”)。然而,尽管大多数数据库都通用 SQL,但执行 SQL 操作的细节各不相同。编写 Python 数据库模块的个人发明了自己的接口,并且由此产生的不同 Python 模块的激增导致了问题:它们没有两个是完全相同的,因此如果您决定切换到新的数据库产品,则必须重写所有检索和插入数据的代码。

为了解决这个问题,成立了一个数据库特别兴趣小组 (SIG)。对将 Python 用于给定应用程序感兴趣的人们会组成自己的 SIG:他们在 Internet 邮件列表上会面,讨论主题、交流想法、编写代码(并调试它),最终生成成品。(听起来很像 Linux 内核的开发过程,不是吗?)

经过一番讨论,数据库 SIG 制定了一个关系数据库的一致接口规范——DB-API。 感谢此规范,您只需学习一个接口。 将代码移植到不同的数据库产品要简单得多,通常只需要更改几行代码。

在数据库 SIG 之前编写的数据库模块仍然存在,并且与规范不符——mSQL 模块是最常用的模块。 这些模块最终将被重写以符合 DB-API; 这只是维护者找到时间去做的问题。

关系数据库

关系数据库由一个或多个表组成。 每个表分为列和行。 列包含相似类型的项目,例如客户 ID 或价格,而行包含单个数据项,每列都有一个值。 单行也称为元组关系,这就是术语“关系数据库”的由来。

对于示例数据库,我们将使用一个小表,该表旨在跟踪一系列研讨会的参与者。(请参阅列表 1。)Seminars 表列出了正在提供的研讨会;示例行是(1,Python Programming,200,15)。每行包含一个唯一的标识 ID 号(在本例中为 1)、研讨会的标题(Python Programming)、其成本(200 美元)以及仍然空缺的位置数量(15)。Attendees 表列出了每位参与者的姓名、他或她希望参加的研讨会以及费用是否已支付。如果有人想参加多个研讨会,则会有多行带有该人的姓名,每行都有不同的研讨会编号和付款状态。

下面的示例使用 soliddb 模块,该模块支持从 Python 访问 SOLID 数据库。SOLID 是 Solidtech 的产品,Bradley Willson 在 1997 年 9 月的 LJ 中对其进行了评测。我不想介绍 CGI 或 Tkinter 编程,因此此处仅介绍访问数据库的命令,方式与直接在 Python 解释器中键入的方式相同。

入门

首先,程序必须首先导入适当的 Python 模块,以连接到正在使用的数据库产品。 按照惯例,所有符合 Python DB-API 的数据库模块的名称都以“db”结尾,例如 soliddb 和 oracledb。

下一步是创建一个表示数据库连接的对象。 该对象的名称与模块相同。 打开连接所需的信息及其格式因数据库而异。 通常,它包括用户名和密码,以及如何查找数据库服务器的一些指示,例如 TCP/IP 主机名。 如果您使用的是 SOLID 的免费试用版,则 UNIX 管道是连接到服务器的唯一可用方法,因此代码是

>>> import soliddb
>>> db = soliddb.soliddb('UPipe SOLID',
         'amk', 'mypassword')
>>> db
<Solid object at 809bf10>
游标对象

接下来,您应该创建一个游标对象。 游标对象充当给定 SQL 查询的句柄; 它允许检索结果的一行或多行,直到处理完所有匹配的行。 对于一次不需要多个查询的简单应用程序,没有必要使用游标对象,因为数据库对象支持与游标对象相同的所有方法。 在下面的示例中,我们将特意使用游标对象。(有关 SQL 入门的更多信息,请参阅 Reuven Lerner 在 1997 年 10 月的 LJ 中撰写的 At the Forge。)

游标对象提供一个 execute() 语句,该语句接受一个包含要执行的 SQL 语句的字符串。 这反过来会导致数据库服务器创建一组与查询匹配的行。

结果通过调用名称以 fetch 开头的方法来检索,如果不再有要检索的行,则返回一个或多个匹配的行或“None”。 fetchone() 方法始终返回单行,而 fetchmany() 返回少量行,fetchall() 返回所有匹配的行。

例如,要列出所有正在提供的研讨会,请执行以下操作

>>> cursor = db.cursor()
>>> # List all the seminars
>>> cursor.execute('select * from Seminars')
>>> cursor.fetchall(
[(4, 'Web Commerce', 300.0, 26),
 (1, 'Python Programming', 200.0, 15),
 (3, 'Socket Programming', 475.0, 7),
 (2, 'Intro to Linux', 100.0, 32),
 ]

行表示为元组,因此返回的第一行是

(4, 'Web Commerce', 300.0, 26)
请注意,行不是按排序顺序返回的;为此,查询必须略有不同(只需添加 order by ID)。 因为它们返回多行,所以 fetchmany() 和 fetchall() 方法返回元组列表。 也可以使用 fetchone() 方法手动迭代结果,并循环直到它返回“None”,如本例所示,该示例列出了研讨会 1 的所有参与者
>>> cursor.execute (
        'select * from Attendees where seminar=1')
>>> while (1):
...  attendee = cursor.fetchone()
...  if attendee == None: break
...  print attendee
...
('Albert', 1, 'no')
('Beth', 1, 'yes')
('Elaine', 1, 'yes')
SQL 还允许您编写对多个表进行操作的查询,如以下查询所示,该查询列出了 Albert 将要参加的研讨会
>>> cursor.execute("""select Seminars.title
...                from Seminars, Attendees
...        where Attendees.name = 'Albert'
...            and Seminars.ID = Attendees.seminar""")
>>&Gt; cursor.fetchall()
[('Python Programming',), ('Web Commerce',)]
既然我们可以从数据库中获取信息,现在是时候开始通过添加新信息来修改它了。 更改是通过使用 SQL insertupdate 语句进行的。 与查询一样,SQL 语句被传递给游标对象的 execute() 方法。
事务

在展示如何添加信息之前,需要注意一个微妙之处,即当任务需要多个不同的 SQL 命令才能完成时,会发生这种情况。 考虑向给定的研讨会添加参与者。 这需要两个步骤。 在一个步骤中,必须将一行添加到 Attendees 表中,给出此人的姓名、他们将参加的研讨会的 ID 以及他们是否已付款。 在另一步骤中,此研讨会的 places_left 值应减一,因为少了一个人的空间。 SQL 无法组合两个命令,因此这需要两次 execute() 调用。 但是,如果发生某些情况并且第二个命令未执行怎么办?也许是因为计算机崩溃、网络中断或 Python 程序中存在拼写错误? 数据库现在不一致:已添加参与者,但该研讨会的 places_left 列现在是错误的。

大多数数据库都提供事务作为解决此问题的方法。 事务是一组命令:要么全部执行,要么都不执行。 程序可以发出多个 SQL 命令作为事务的一部分,然后 提交 它们(即,告诉数据库同时应用所有这些更改)。 或者,程序可以确定出现问题并回滚事务而不进行更改。

对于支持事务的数据库,Python 接口在创建游标时会默默地启动一个事务。 commit() 方法提交使用该游标所做的更新,rollback() 方法放弃它们。 然后,每个方法都会启动一个新的事务。 某些数据库没有事务,而是简单地在执行时应用所有更改。 在这些数据库上,commit() 不执行任何操作,但您仍然应该调用它,以便与那些确实支持事务的数据库兼容。

列表 2 是一个 Python 函数,它试图通过在执行完两个操作后提交事务来正确完成所有这些操作。 调用此函数很简单

addAttendee('George', 4, 'yes')

我们可以通过检查研讨会 #4 的列表并列出其参与者来验证是否执行了更改。 这会产生以下输出

Seminars:
4       'Web Commerce'  300.0   25
Attendees:
Albert  4       no
Dale    4       yes
Felix   4       no
George  4       yes
请注意,如果多个进程或线程尝试同时执行此函数,则此函数仍然存在错误。 数据库编程可能非常复杂。

有了这个标准化的接口,编写各种 Python 程序作为数据库的易于使用的前端并不困难。

资源

Andrew Kuchling 在华盛顿特区的 Magnet Interactive 担任网站开发人员。他过去的项目之一是一个相当大的商业网站,该网站是使用 Python 在 Illustra 数据库之上实现的。可以通过电子邮件 akuchling@acm.org 与他联系。

加载 Disqus 评论