6.8 同关系型数据库进行交互
6.8.1 问题
我们需要选择、插入或者删除关系型数据库中的行数据。
6.8.2 解决方案
在Python中,表达行数据的标准方式是采用元组序列。例如:
stocks = [
('GOOG', 100, 490.1),
('AAPL', 50, 545.75),
('FB', 150, 7.45),
('HPQ', 75, 33.2),
]
当数据以这种形式呈现时,通过Python标准的数据库API(在PEP 249中描述)来同关系型数据库进行交互相对来说就显得很直接了。该API的要点就是数据库上的所有操作都通过SQL查询来实现。每一行输入或输出数据都由一个元组来表示。
为了说明,我们可以使用Python自带的sqlite3模块。如果正在使用一个不同的数据库(如MySQL、Postgres或者ODBC),就需要安装一个第三方的模块来支持。但是,底层的编程接口即使不完全相同的话也几乎是一致的。
第一步是连接数据库。一般来说,要调用一个connect()函数,提供类似数据库名称、主机名、用户名、密码这样的参数以及一些其他需要的细节。示例如下:
>>> import sqlite3
>>> db = sqlite3.connect('database.db')
>>>
要操作数据的话,下一步就要创建一个游标(cursor)。一旦有了游标,就可以开始执行SQL查询了。示例如下:
>>> c = db.cursor()
>>> c.execute('create table portfolio (symbol text, shares integer, price real)')
<sqlite3.Cursor object at 0x10067a730>
>>> db.commit()
>>>
要在数据中插入行序列,可以采用这样的语句:
>>> c.executemany('insert into portfolio values (?,?,?)', stocks)
<sqlite3.Cursor object at 0x10067a730>
>>> db.commit()
>>>
要执行查询操作,可以使用下面这样的语句:
>>> for row in db.execute('select * from portfolio'):
... print(row)
...
('GOOG', 100, 490.1)
('AAPL', 50, 545.75)
('FB', 150, 7.45)
('HPQ', 75, 33.2)
>>>
如果想执行的查询操作需要接受用户提供的输入参数,请确保用?隔开参数,就像下面的示例这样:
>>> min_price = 100
>>> for row in db.execute('select * from portfolio where price >= ?',
(min_price,)):
... print(row)
...
('GOOG', 100, 490.1)
('AAPL', 50, 545.75)
>>>
6.8.3 讨论
从较低的层次来看,同数据库的交互其实是一件非常直截了当的事。只要组成SQL语句然后将它们传给底层的模块就可以更新数据库或者取出数据了。尽管如此,这里还是有一些比较棘手的细节问题需要针对每种情况逐项考虑。
其中一个比较复杂的问题就是将数据库中的数据映射到Python的类型中。对于像日期这样的条目,最常见的是使用datetime模块中的datetime实例,或者也可能是time模块中用到的系统时间戳(system timestamps)。对于数值型的数据,尤其是涉及小数的金融数据,这些数字可以用decimal模块中的Decimal实例来表示。不幸的是,确切的映射关系会因数据库后端的不同而有所区别,因此必须去阅读相关的文档。
另一个极其重要的问题是需要考虑组成SQL语句的字符串。我们绝对不应该用Python的字符串格式化操作符(即%)或者.format()方法来创建这种字符串。如果给这样的格式化操作符提供的值是来自于用户的输入,那么这就等于将你的程序敞开大门迎接SQL注入攻击(参见http://xkcd.com/327 )。在查询操作中,特殊的?通配符会指示数据库后端启用自己的字符串替换机制,这样才能做到安全(希望如此)。
可悲的是,数据库后端对通配符的支持并不一致。有许多模块采用的是?或%s,而其他一些可能会使用不同的符号,比如用:0或者:1来代表参数。这里再次说明,必须查阅正在使用的数据库模块的文档资料。数据库模块的paramstyle属性中也包含有关于引用样式的相关信息。
对于简单地三趸将数据从数据库表项中取出和输入,使用数据库API通常足够了。如果要处理更加复杂的任务,那么使用一种更高层次的接口就显得很有意义了,比如那些提供有对象关系映射组件(object-relational mapper,ORM)的接口。像SQLAlchemy(http://www.sqlalchemy.org )这样的库允许数据库表项以Python类的形式来描述,在执行数据库操作时可隐藏大部分底层的SQL。