使用简单的纯文本文件可实现的功能有限。诚然,使用它们可做很多事情,但有时可能还需要额外的功能。你可能希望能够自动完成序列化,此时可求助于shelve和pickle(类似于shelve)。不过你可能需要比这更强大的功能。例如,你可能想自动支持数据并发访问,即允许多位用户读写磁盘数据,而不会导致文件受损之类的问题。还有可能希望同时根据多个数据字段或属性进行复杂的搜索,而不是采用shelve提供的简单的单键查找。尽管可供选择的解决方案有很多,但如果要处理大量的数据,并希望解决方案易于其他程序员理解,选择较标准的数据库可能是个不错的主意。
本章讨论Python数据库API(一种连接到SQL数据库的标准化方式),并演示如何使用这个API来执行一些基本的SQL。最后,本章将讨论其他一些数据库技术。
这里不会提供关系型数据库和SQL语言教程。通过阅读有关数据库(如PostgreSQL、MySQL或本章使用的SQLite)的文档,就应该能够学到你需要知道的知识。如果你以前没有使用过关系型数据库,可参阅www.sqlcourse.com或在网上搜索相关的主题,也可参阅Clare Churcher的著作Beginning SQL Queries, 2nd ed(Apress,2016)。
本章使用的是简单数据库SQLite,但显然绝非只能使用它。有多种流行的商用数据库,如Oracle和Microsoft SQL Server,还有一些使用广泛而且可靠的开源数据库,如MySQL、PostgreSQL和Firebird。有关Python支持的数据库清单,请参阅https://wiki.python.org/moin/DatabaseInterfaces。数据库也并非只有关系型(SQL)这一种,还有对象数据库[如Zope Object Database(ZODB,http://zodb.org)]、基于表格的紧凑数据库[如Metakit(http://equi4.com/metakit)]、更简单的键-值数据库[如UNIX DBM(https://docs.python.org/3/library/dbm.html)]。另外,还有日益流行的各种NoSQL数据库,如MongoDB(http://mongodb.com)、Cassandra(http://cassandra.apache.org)和Redis(http://redis.io),这些数据库都可使用Python来访问。
本章的重点是低级的数据库交互,但有一些高级库能够让你轻松地完成复杂的工作,要获悉这方面的信息,可参阅http://sqlalchemy.org或http://sqlobject.org,也可在网上搜索Python对象-关系映射器。
Python数据库API
前面说过,有各种SQL数据库可供选择,其中很多都有相应的Python客户端模块(有些数据库甚至有多个)。所有数据库的大多数基本功能都相同,因此从理论上说,对于使用其中一种数据库的程序,很容易对其进行修改以使用另一种数据库。问题是即便不同模块提供的功能大致相同,它们的接口(API)也是不同的。为解决Python数据库模块存在的这种问题,人们一致同意开发一个标准数据库API(DB API)。这个API的最新版本(2.0)是在PEP 249(Python Database API Specification v2.0)中定义的,网址为http://python.org/peps/pep-0249.html。本节概述有关该API的基础知识。这里不会涉及其可选部分,因为它们并不适用于所有数据库。有关该API的详细信息,可参阅前面提到的PEP,也可参阅Python官方维基百科中的数据库编程指南(http://wiki.python.org/moin/DatabaseProgramming)。如果你对这个API的细节不感兴趣,可跳过本节。
全局变量
所有与DB API2.0兼容的数据库模块都必须包含三个全局变量,它们描述了模块的特征。这样做的原因是,这个API设计得很灵活,无需进行太多包装就能配合多种不同的底层机制使用。如果要让程序能够使用多种不同的数据库,可能会比较麻烦,因为需要考虑众多不同的可能性。在很多情况下,一种更现实的做法是检查这些变量,看看给定的模块是否是程序能够接受的。如果不是,就显示合适的错误消息并退出或者引发异常。表13-1总结了这些全局变量。
表13-1 Python DB API的模块属性
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表示线程不能共享模块,而3表示模块是绝对线程安全的。1表示线程可共享模块本身,但不能共享连接(参见13.1.3节),而2表示线程可共享模块和连接,但不能共享游标。如果你不使用线程(在大多数情况下可能不会是这样的),就根本不用关心这个变量。
参数风格(paramstyle)表示当你执行多个类似的数据库查询时,如何在SQL查询中插入参数。'format’表示标准字符串格式设置方式(使用基本的格式编码),如在要插入参数的地方插入%s。'pyformat’表示扩展的格式编码,即旧式字典插入使用的格式编码,如%(foo)s。除这些Python风格外,还有三种指定待插入字段的方式:'qmark’表示使用问号,'numeric’表示使用:1和:2这样的形式表示字段(其中的数字是参数的编号),而’named’表示使用:foobar这样的形式表示字段(其中foobar为参数名)。如果你觉得参数样式令人迷惑,也不用担心。编写简单程序时,不会用到它们。如果需要明白特定的数据库是如何处理参数的,可参阅相关的文档。
异常
DB API定义了多种异常,让你能够细致地处理错误。然而,这些异常构成了一个层次结构,因此使用一个except块就可捕获多种异常。当然,如果你觉得一切都正常运行,且不介意出现不太可能出现的错误时关闭程序,可以根本不考虑这些异常。表13-2说明了这个异常层次结构。异常应该在整个数据库模块中都可用。有关这些异常的深入描述,请参阅DB API规范(前面提到的PEP)。
表13-2 Python DB API指定的异常
连接和游标
要使用底层的数据库系统,必须先连接到它,为此可使用名称贴切的函数connect。这个函数接受多个参数,具体是哪些取决于要使用的数据库。作为指南,DB API定义了表13-3所示的参数。推荐将这些参数定义为关键字参数,并按表13-3所示的顺序排列。这些参数都应该是字符串。
表13-3 函数connect的常用参数
函数connect返回一个连接对象,表示当前到数据库的会话。连接对象支持表13-4所示的方法。
表13-4 连接对象的方法
方法rollback可能不可用,因为并非所有的数据库都支持事务(事务其实就是一系列操作)。可用时,这个方法撤销所有未提交的事务。
方法commit总是可用的,但如果数据库不支持事务,这个方法就什么都不做。关闭连接时,如果还有未提交的事务,将隐式地回滚它们——但仅当数据库支持回滚时才如此!如果你不想依赖于这一点,应在关闭连接前提交。只要提交了所有的事务,就无需操心关闭连接的事情,因为作为垃圾被收集时,连接会自动关闭。然而,为安全起见,还是调用close吧,因为这样做不需要长时间敲击键盘。
说到方法cursor,就必须说说另一个主题:游标对象。你使用游标来执行SQL查询和查看结果。游标支持的方法比连接多,在程序中的地位也可能重要得多。表13-5概述了游标的方法,而表13-6概述了游标的属性。
表13-5 游标对象的方法
表13-6 游标对象的属性
有些方法将在本章后面详细讨论,还有一些(如setinputsizes和setoutputsizes)则不会讨论。有关这些方法的详细信息,请参阅前面提到的PEP。
类型
对于插入到某些类型的列中的值,底层SQL数据库可能要求它们满足一定的条件。为了能够与底层SQL数据库正确地互操作,DB API定义了一些构造函数和常量(单例),用于提供特殊的类型和值。例如,要在数据库中添加日期,应使用相应数据库连接模块中的构造函数Date来创建它,这让连接模块能够在幕后执行必要的转换。每个模块都必须实现表13-7所示的构造函数和特殊值。有些模块可能没有完全遵守这一点。例如,接下来将讨论的模块sqlite3就没有导出表13-7中特殊值(从STRING到ROWID)。
表13-7 DB API构造函数和特殊值