跳转至

15.1.DBAPI

为了解决Python数据库模块接口不统一的问题,人们一致同意开发一个标准数据库API——DB API。这个API的最新版本为2.0,在PEP 249 (Python Database API Specification v2.0) 中定义 。

全局变量

所有与DB API2.0兼容的数据库模块都必须包含三个全局变量,它们描述了模块的特征。这样做的原因是,这个API设计得很灵活,无需进行太多包装就能配合多种不同的底层机制使用。如果要让程序能够使用多种不同的数据库,可能会比较麻烦,因为需要考虑众多不同的可能性。在很多情况下,一种更现实的做法是检查这些变量,看看给定的模块是否是程序能够接受的。如果不是,就显示合适的错误消息并退出或者引发异常。下表总结了这些全局变量。

变 量 名 描 述
apilevel 使用的Python DB API版本
threadsafety 模块的线程安全程度如何
paramstyle 在SQL查询中使用哪种参数风格

API级别( apilevel )是一个字符串常量,指出了使用的API版本。DB API 2.0指出,这个变量的值为 '1.0''2.0' 。如果没有这个变量,就说明模块不与DB API 2.0兼容,此时应假定模块使用的是DB API 1.0。编写代码时,允许这个变量为其他值也没有害处,因为说不定什么时候DB API 3.0就出来了。

线程安全程度( threadsafety )是一个 0~3 的整数。如果你不使用多线程,则不必关心这个变量。

  • 0 表示线程不能共享模块
  • 1 表示线程可共享模块本身,但不能共享连接
  • 2 表示线程可共享模块和连接,但不能共享游标
  • 3 表示模块是绝对线程安全的,模块、连接和游标都可以共享

参数风格( paramstyle )表示当你执行多个类似的数据库查询时,如何在SQL查询中插入参数。

  • 'format'表示标准字符串格式设置方式(使用基本的格式编码),如在要插入参数的地方插入 %s

  • 'pyformat'表示扩展的格式编码,即旧式字典插入使用的格式编码,如%(foo)s

  • 'qmark' 表示使用问号

  • 'numeric' 表示使用 :1:2 这样的形式表示字段(其中的数字是参数的编号)

  • 'named' 表示使用 :foobar 这样的形式表示字段(其中 foobar 为参数名)

其中,'format''pyformat'属于Python风格。如果你觉得参数样式令人迷惑,也不用担心。编写简单程序时,不会用到它们。如果需要明白特定的数据库是如何处理参数的,可参阅相关的文档。

异常

DB API定义了多种异常,让你能够细致地处理错误。然而,这些异常构成了一个层次结构,因此使用一个 except 块就可捕获多种异常。当然,如果你觉得一切都正常运行,且不介意出现不太可能出现的错误时关闭程序,可以根本不考虑这些异常。表1说明了这个异常层次结构。异常应该在整个数据库模块中都可用。有关这些异常的深入描述,请参阅DB API规范(前面提到的PEP)。 $$ \begin{array}{lll} & \text { 表1 Python DB API 指定的异常 } & \\ \hline \text { 异 常} & \text { 超 类} & \text { 描 述 } \\ \hline \text { StandardError } & & \text { 所有异常的超类 } \\ \text { Warning } & \text { StandardError } & \text { 发生非致命问題时引发 } \\ \text { Error } & \text { StandardError } & \text { 所有错误条件的超类 } \\ \text { InterfaceError } & \text { Error } & \text { 与接口 ( 而不是数据库 }) \text { 相关的错误 } \\ \text { DatabaseError } & \text { Error } & \text { 与数据库相关的错误的超类 } \\ \text { Datafrror } & \text { DatabaseError } & \text { 与数据相关的问题, 如值不在合法的范围内 } \\ \text { OperationalError } & \text { Databasefror } & \text { 数据库操作内部的错误 } \\\ \text { IntegrityError } & \text { Databasefror } & \text { 关系完整性還到破坏, 如键术通过检查 } \\ \text { InternalError } & \text { Databasefror } & \text { 数据库内部的错误, 如游标无效 } \\ \text { ProgrammingError } & \text { Databasefror } & \text { 用户编程错误, 如未找到数据库表 } \\ \text { NotsupportedError } & \text { Databasefror } & \text { 请求不支持的功能,如回滚 } \\ \hline \end{array} $$

连接和游标

要使用底层的数据库系统,必须先连接到它,为此可使用名称贴切的函数 connect 。这个函数接受多个参数,具体是哪些取决于要使用的数据库。作为指南,DB API 定义了表2所示的参数。推荐将这些参数定义为关键字参数,并按下表所示的顺序排列。这些参数都应该是字符串。 $$ \begin{array}{clcc} & \text { 表2 函数 connect 的常用参数 } & \\ \hline \text { 参 数 名} & \text { 描 述} & \text { 是否可选 } & \\ \hline \text { dsn } & \text { 数据源名称, 具体含义随数据库而异 } & \text { 否 } \\ \text { user } & \text { 用户名 } & \text { 是 } \\ \text { password } & \text { 用户密码 } & \text { 是 } \\ \text { host } & \text { 主机名 } & \text { 是 } \\ \text { database } & \text { 数据库名称 } & \text { 是 } \\ \hline \end{array} $$ 函数 connect 返回一个连接对象,表示当前到数据库的会话。连接对象支持下表所示的方法。 $$ \begin{array}{ll} & \text {表3 连接对象的方法 } & \\ \hline \text { 方 法 名} & \text {描 述 } \\ \hline \text { close() } & \text{关闭连接对象。之后, 连接对象及其游标将不可用} \\ \text { commit() } & \text{提交未提交的事务, 如果支持的话;否则什么都不做} \\ \text { rollback() } & \text {回滚未提交的事务(可能不可用)} \\ \text { cursor() } & \text {返回连接的游标对象} \\ \hline\end{array} $$ 方法 rollback 可能不可用,因为并非所有的数据库都支持事务(事务其实就是一系列操作)。可用时,这个方法撤销所有未提交的事务。

方法 commit 总是可用的,但如果数据库不支持事务,这个方法就什么都不做。关闭连接时,如果还有未提交的事务,将隐式地回滚它们——但仅当数据库支持回滚时才如此!如果你不想依赖于这一点,应在关闭连接前提交。只要提交了所有的事务,就无需操心关闭连接的事情,因为作为垃圾被收集时,连接会自动关闭。然而,为安全起见,还是调用 close 吧,因为这样做不需要长时间敲击键盘。

说到方法 cursor,就必须说说另一个主题:游标对象你使用游标来执行SQL查询和查看结果。 游标支持的方法比连接多,在程序中的地位也可能重要得多。表4概述了游标的方法,而表5概述了游标的属性。 $$ \begin{array}{ll} & \text { 表4 游标对象的方法 } \\ \hline \text {名 称} & \text {描 述} \\ \hline \text { callproc(name[, params]) } & \text { 使用指定的参数调用指定的数据库过程 }(\text { 可选 }) \\ \text { close() } & \text { 关闭游标。关闭后游标不可用 } \\ \text { execute(oper[, params]) } & \text { 执行一个SQL操作一一可能指定参数 } \\ \text { executemany(oper, pseq) } & \text { 执行指定的SQL操作多次, 每次都序列中的一组参数 } \\ \text { fetchone() } & \text { 以序列的方式取回查询结果中的下一行 } ; \text { 如果没有更多的行, 就返回None } \\ \text { fetchmany([size]) } & \text { 取回查询结果中的多行, 其中参数size的值默认为arraysize } \\ \text { fetchall() } & \text { 以序列的序列的方式取回余下的所有行 } \\ \text { nextset() } & \text { 跳到下一个结果集,这个方法是可选的 } \\ \text { setinputsizes(sizes) } & \text { 用于为参数顶定义内存区域 } \\ \text { setoutputsize(size[, col]) } & \text { 为取回大量数据而设置缓冲区长度 } \\ \hline \end{array} $$

$$ \begin{array}{cc} &\hspace{-7em} \text{表5 游标对象的属性 }\\ \hline \text { 名 称 } & \text { 描 述 } \\ \hline \text { description } & \text { 由结果列描述组成的序列 (只读 }) & \\ \text { rowcount } & \text { 结果包含的行数 ( } \text { 只读 }) & \\ \text { arraysize } & \text { fetchmany返回的行数, 默认为1 } & \\ \hline \end{array} $$

类型

对于插入到某些类型的列中的值,底层SQL数据库可能要求它们满足一定的条件。为了能够与底层SQL数据库正确地互操作,DB API定义了一些构造函数和常量(单例),用于提供特殊的类型和值。例如,要在数据库中添加日期,应使用相应数据库连接模块中的构造函数 Date 来创建它,这让连接模块能够在幕后执行必要的转换。每个模块都必须实现表6所示的构造函数和特殊值。有些模块可能没有完全遵守这一点。例如,sqlite3 就没有导出表6中特殊值(从 STRING 到 ROWID)。

$$ \begin{array}{ll} & \hspace{-5em} \text{ 表6 DB API构造函数和特殊值 } \\ \hline \text { 名 称 } & \text { 描 述} \\ \hline \text { Date( year, month, day }) \\ \text { Time( hour, minute, second) } & \text { 创建包含日期值的对象 } \\ \text { Timestamp( y, mon, d, h, min, s} ) & \text { 创建含时间值的对象 } \\ \text { DateFromTicks( ticks ) } & \text { 根据从新纪元开始过去的秒数创建包含日期值的对象 } \\ \text { TimeFromTicks( ticks ) } & \text { 根据从新纪元开始过去的秒数创建包含时间值的对象 } \\ \text { imestampFromTicks( ticks ) } & \text { 根据从新纪元开始过去的秒数创建包含时间戳的对象 } \\ \text { Binary( string ) } & \text { 创建包含二进制字符串值的对象 } & \\ \text { STRING } & \text { 描述基于字符串的列 }(\text { 如CHAR }) & \\ \text { BINARY } & \text { 描述二进制列 }(\text { 如LONG或RAW }) & \\ \text { NUMBER } & \text { 描述数字列 } \\ \text { DATETIME } & \text { 描述日期/时间列 } \\ \text { ROWID } & \text { 描述行ID列 } \\ \hline \end{array} $$