准备:今天拿笔记本装了mysql,这样就能在不同地方用其他电脑远程访问同一个数据库了。
21.1 介绍
本章的主题是如何在大型项目中使用关系型数据库。本章对数据库知识和SQL语言不做介绍,本章从一个Python应用程序开始,了解在Python框架下,如何将数据保存到数据库,如何将数据从数据库中取出来。“在Python世界里,无需怀疑,与数据库协同工作已经几乎是所有应用程序的核心部分了。”作者原话。
所有Python接口程序都一定程度上遵守Python DB-API 规范。
21.2 Python数据库应用程序
什么是DB-API?DB-API是一个规范。它定义了一系列必须的对象和数据库存取方式,以便为各种各样的底层数据库系统和多种多样的数据库接口程序提供一致的访问接口。
下面是一个例子:
#-*- coding:utf-8 -*-
importMySQLdb as SQL#首先需要与数据库连接
cxn = SQL.connect(host='192.168.1.105', #这里是自己数据库主机地址
user='xxx', #用户名
passwd='xxxx', #密码
db='JiaoYanShi', #所用数据库名称
port=3306, #mysql默认端口
charset='utf8') #编码方式#连接好以后与数据库进行交互需要创建游标对象,一个游标允许用户执行数据库命令和得到查询结果。
cur =cxn.cursor()#创建一个表
cur.execute('CREATE TABLE IF NOT EXISTS users(login VARCHAR(8),uid INT)')#插入数据
cur.execute("INSERT INTO users VALUES('john',7000)")
cur.execute("INSERT INTO users VALUES('jane',7001)")
cur.execute("INSERT INTO users VALUES('bob',7200)")#执行execute以后数据已经进入了数据库,但是如果没有下面的事务提交语句,已经进入的数据就会回滚,导致数据库中不会有数据#python 操作mysql 是用 事物 的方式来实现的,所以必须有commit 提交的过程,否则数据表不会生效#然而commit原理还没怎么懂…这样commit()不知道是否妥当,后续继续研究cxn.commit()#必须进行select以后才会有打印结果
cur.execute("SELECT * FROM users")
content=cur.fetchall()for data incontent:printdataprint '-'*50
#下面进行表的更新
cur.execute("UPDATE users SET uid = 7100 WHERE uid = 7001")
cxn.commit()
cur.execute("SELECT * FROM users")
content=cur.fetchall()for data incontent:printdataprint '-'*50#下面进行表数据删除cur.execute("DELETE FROM users WHERE login = 'bob'")
cxn.commit()
cur.execute("SELECT * FROM users")
content=cur.fetchall()for data incontent:printdata#关闭游标对象
cur.close()#关闭连接
cxn.close()>>>(u'john', 7000L)
(u'jane', 7001L)
(u'bob', 7200L)--------------------------------------------------(u'john', 7000L)
(u'jane', 7100L)
(u'bob', 7200L)--------------------------------------------------(u'john', 7000L)
(u'jane', 7100L)
[Finishedin 0.3s]
下面的这个例子展示了数据库数据通讯的一般框架:
#-*- coding:utf-8 -*-
importosfrom random importrandrange as rrange
COLSIZ= 10RDBMSs= {'s': 'sqlite', 'm': 'mysql', 'g': 'gadfly'}
DB_EXC=None
HOST='xxx.xxx.xxx.xxx' #这里是HOST地址
User='xxxx' #用户名
Passwd='xxxxxx' #密码
Port=3306 #mysql默认端口
Charset='utf8' #编码方式
#下面这个函数是选择数据库类型
defsetup():return RDBMSs[raw_input('''Choose a database system:
(M)ySQL
(G)adfly
(S)QLite
Enter choice:''').strip().lower()[0]]#下面是返回一个database.connect连接对象
defconnect(db, dbName):globalDB_EXC
dbDir= '%s_%s' %(db, dbName)if db == 'sqlite':try:importsqlite3exceptImportError, e:try:from pysqlite2 importdbapi2 as sqlite3exceptImportError, e:returnNone
DB_EXC=sqlite3if notos.path.isdir(dbDir):
os.mkdir(dbDir)
cxn=sqlite3.connect(os.path.join(dbDir, dbName))elif db == 'mysql':try:importMySQLdb#下面这个模块是专门用来进行数据库错误处理的
import_mysql_exceptions as DB_EXCexceptImportError, e:returnNone#下面的处理时用来创建数据库(如果不存在),可以作为一个标准处理流程
try:
cxn= MySQLdb.connect(host =HOST,
user=User,
passwd=Passwd,
db=dbName,
port=Port,
charset='utf8')#连接不成功进行如下处理
exceptDB_EXC.OperationalError, e:#先连接一下数据库地址,不写数据库名称
cxn = MySQLdb.connect(host =HOST,
user=User,
passwd=Passwd,
port=Port,
charset='utf8')try:
cxn.query('DROP DATABASE %s' %dbName)exceptDB_EXC.OperationalError, e:pass
#创建数据库
cxn.query('CREATE DATABASE %s' %dbName)
cxn.commit()
cxn.close()#连接上数据库
cxn = MySQLdb.connect(host =HOST,
user=User,
passwd=Passwd,
db=dbName,
port=Port,
charset='utf8')elif db == 'gadfly':try:from gadfly importgadfly
DB_EXC=gadflyexceptImportError, e:returnNonetry:
cxn=gadfly(dbName, dbDir)exceptIOError, e:
cxn=gadfly()if notos.path.isdir(dbDir):
os.mkdir(dbDir)
cxn.startup(dbName, dbDir)else:returnNonereturncxn
drop= lambda cur: cur.execute('DROP TABLE users')defcreate(cur):#创建新的表users
try:
cur.execute('''CREATE TABLE users (
login VARCHAR(8),
uid INTEGER,
prid INTEGER)''')exceptDB_EXC.OperationalError, e:
drop(cur)
create(cur)#插入的数据
NAMES =(
('aaron', 8312), ('angela', 7603), ('dave', 7306),
('davina',7902), ('elliot', 7911), ('ernie', 7410),
('jess', 7912), ('jim', 7512), ('larry', 7311),
('leslie', 7808), ('melissa', 8602), ('pat', 7711),
('serena', 7003), ('stan', 7607), ('faye', 6812),
('amy', 7209),
)#下面是一个创建函数,当调用此函数时,得到一个生成器
defrandName():
pick=list(NAMES)while len(pick) >0:yieldpick.pop(rrange(len(pick)))#下面的函数进行数据库中表users的写入操作
definsert(cur, db):if db == 'sqlite':
cur.executemany("INSERT INTO users VALUES(?, ?, ?)",
[(who, uid, rrange(1,5)) for who, uid inrandName()])elif db == 'gadfly':for who, uid inrandName():
cur.execute("INSERT INTO users VALUES(?, ?, ?)",
(who, uid, rrange(1,5)))#下面将NAMES写进数据库
elif db == 'mysql':
cur.executemany("INSERT INTO users VALUES(%s, %s, %s)",
[(who, uid, rrange(1,5)) for who, uid inrandName()])#cur.rowcount用来记录最后一次execute影响的行数
getRC = lambda cur: cur.rowcount if hasattr(cur, 'rowcount') else -1
#下面执行数据更新
defupdate(cur):
fr= rrange(1,5)
to= rrange(1,5)
cur.execute("UPDATE users SET prid=%d WHERE prid=%d" %(to, fr))returnfr, to, getRC(cur)#随机删除一些行
defdelete(cur):
rm= rrange(1,5)
cur.execute('DELETE FROM users WHERE prid=%d' %rm)returnrm, getRC(cur)defdbDump(cur):
cur.execute('SELECT * FROM users')print '\n%s%s%s' % ('LOGIN'.ljust(COLSIZ),'USERID'.ljust(COLSIZ), 'PROJ#'.ljust(COLSIZ))for data incur.fetchall():print '%s%s%s' % tuple([str(s).title().ljust(COLSIZ) for s indata])defmain():
db=setup()print '*** Connecting to %r database' %db
cxn= connect(db, 'test')if notcxn:print '\nERROR: %r not supported, exiting' %dbreturn
#游标对象
cur =cxn.cursor()print '\n*** Creating users table'create(cur)print '\n*** Inserting names into table'insert(cur, db)
dbDump(cur)print '\n*** Randomly moving folks',
fr, to, num=update(cur)print 'from one group (%d) to another (%d)' %(fr, to)print '\t(%d users moved)' %num
dbDump(cur)print '\n*** Randomly choosing group',
rm, num=delete(cur)print '(%d) to delete' %rmprint '\t(%d users removed)' %num
dbDump(cur)print '\n*** Dropping users table'drop(cur)
cur.close()
cxn.commit()
cxn.close()if __name__ == '__main__':
main()
上面的例子只是应用了MySQL数据库,其他两个没试验。
21.3 对象-关系管理器(ORM)
ORM即对象-映射关系(OBJECT/RELATION MAPPING),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。也就是将Python对象与SQL进行映射转换。
如果用ORM,那么需要尽量避免直接的SQL查询。数据库中的表被转换为Python类,它具有列属性和操作数据库的方法。
最知名的Python ORM模块是SQLAlchemy 和 SQLObject。书上说两者有着不同的设计哲学...
1、SQLAlchemy的接口在某种程度上更接近SQL。SQLAlchemy的抽象层‘相当完美’,并且在必须用SQL完成某些功能时,它提供了足够的灵活性。‘其实这两个ORM模块在设置及存取数据时使用的术语非常相似,代码长度也相似。’
这部分课本上的代码过时了。。