菜单

Python DB-API开发MySQL脚本

2011年03月11日 - Python
本文将向读者详细介绍如何利用DB-API模块方便地开发MySQL脚本。我们首先介绍了有关软件的安装方法,随后分别用两个实例对DB-API模块做了深入的介绍,最后讨论了用Python DB-API编写的MySQL脚本的移植注意事项。

一、DB-API简介

Python语言是一种流行的开源编程语言,不仅自身表达能力强,而且还有许多辅助模块,着极大的增强了其功能。本文介绍的是一个名为DB-API的模块——顾名思义,该模块为开发人员提供了数据库应用编程接口。DB-API旨在帮应用开发人员摆脱特定数据库引擎的具体细节的纠缠,编写出能够在不同数据库引擎之间相互移植的数据库访问脚本。

DB-API的设计与Perl和Ruby的DBI模块、PHP 的PEAR DB 类以及Java的JDBC接口非常类似,它使用了一个两层体系结构,其中顶层提供了一个抽象接口,该接口可以泛用于所有支持的数据库引擎;而底层则是有一些特定引擎的驱动程序构成,这些驱动程序负责处理与特定引擎有关的细节。这样的话,为了使用DB-API来编写Python脚本,必须有相应的驱动程序来处理特定的数据库系统。对于MySQL来说,DB-API使用驱动程序MySQLdb来处理对MySQL数据库的访问。下面我们将首先详细介绍驱动程序的安装,然后阐述如何编写DB-API脚本。

二、安装MySQLdb

为了用DB-API编写MySQL脚本,必须确保已经安装了Python。对于UNIX操作系统来说,通常都安装有该软件,但是Windows系统的话就难说了。其实Python的安装很简单,先到Python官方网站下载安装程序,然后一路回车即可。

就本文而言,我们要求使用Python 2.3.4或更高版本,并且还得安装MySQLdb模块。我们可以从命令行提示符(这里的提示符为UNIX操作系统中的%,如果是Windows系统的话,该提示符则为C:\>)中输入下列命令来检查两者的安装情况,命令如下所示:

  1. % python
  2. Python 2.4.3 (#1, Aug 29 2006, 14:45:33)
  3. [GCC 3.4.6 (Gentoo 3.4.6-r1, ssp-3.4.5-1.0, pie-8.7.9)] on linux2
  4. Type “help”, “copyright”, “credits” or “license” for more information.
  5. >>> import MySQLdb

复制代码

假如Python的版本满足要求,并且输入import MySQLdb命令(即导入MySQLdb模块)后没有出现错误报告的话,我们就可以开始编写访问数据库的脚本了;但是,如果出现下列错误消息的话,则说明还缺少MySQLdb,所以必须先下载安装MySQLdb模块:

  1. >>> import MySQLdb
  2. Traceback (most recent call last):
  3. File “”, line 1, in ?
  4. ImportError: No module named MySQLdb

复制代码

为了获得MySQLdb,请访问http://sourceforge.net/projects/mysql-python,从这里可选择适合您的平台的安装包,分为预编译的二进制文件和源代码安装包。如果您选择二进制文件发行版本的话,安装过程基本安装提示即可完成。如果从源代码进行安装的话,则需要切换到MySQLdb发行版本的顶级目录,并键入下列命令。在UNIX操作系统之下安装的时候,第二条命令需要以超级用户的身份运行才能将驱动程序文件复制到Python的安装目录中。具体命令如下所示:

  1. % python setup.py build
  2. % python setup.py install

复制代码

如果安装时遇到问题,您可以阅读MySQLdb发行版本中的自述文件来寻找答案。

三、我们第一个DB-API脚本

脚本使用MySQLdb的DB-API访问MySQL的时候,通常要经过以下步骤:

  • 导入MySQLdb模块
  • 打开一个MySQL服务器连接
  • 发出查询语句并回收查询结果
  • 关闭服务器连接

下面我们通过一个简单的DB-API脚本对这些步骤进行说明。编写脚本时,可以使用文本编辑程序建立一个文件,将其命名为 server_version.py,然后用它来存放下面的脚本。这个脚本利用MySQLdb实现与MySQL服务器的交互,它的任务很简单,那就是询问服务器的版本字符串:

  1. # server_version.py – 检索并显示数据库服务器版本
  2. import MySQLdb
  3. conn = MySQLdb.connect (host = “localhost”,
  4. user = “testuser”,
  5. passwd = “testpass”,
  6. db = “test”)
  7. cursor = conn.cursor ()
  8. cursor.execute (“SELECT VERSION()”)
  9. row = cursor.fetchone ()
  10. print “server version:”, row[0]
  11. cursor.close ()
  12. conn.close ()

复制代码

Import语句告诉Python该脚本需要MySQLdb模块中的代码。注意,导入语句必须放置在连接MySQL服务器的语句之前。然后,使用相应连接参数来调用MySQLdb驱动程序的connect()方法,这些参数包括运行MySQL的服务器的主机名,我们MySQL帐户对应的用户名和口令,以及我们要使用的数据库的名称。 Connect()的参数语法会随着驱动程序的不同而不同,对于MySQLdb来说,这些参数可以通过name = value的格式提供,这样的话,我们就可以不同的顺序来提供参数。server_version.py会在本地主机上建立一个到达MySQL服务器的连接,并利用用户名和口令即testuser和testpass来访问我们的数据库test,如下所示:

  1. conn = MySQLdb.connect (host = “localhost”,
  2. user = “testuser”,
  3. passwd = “testpass”,
  4. db = “test”)

复制代码

如果调用connect()成功,它会返回一个连接对象,为进一步与MySQL交互做好准备。如果调用失败,它会抛出一个异常。注意,我们的server_version.py脚步并没有处理该异常,所以碰到错误时该脚本就会终止。具体的错误处理将在后面加以介绍。

获得连接对象之后,server_version.py会调用该对象的cursor()方法来创建一个游标对象来处理语句。该脚本利用这个游标发送一个SELECT VERSION()语句,该语句会返回一个包含有服务器版本信息的字符串,如下所示:

  1. cursor = conn.cursor ()
  2. cursor.execute (“SELECT VERSION()”)
  3. row = cursor.fetchone ()
  4. print “server version:”, row[0]
  5. cursor.close ()

复制代码

该游标对象的execute()方法能将向服务器发送语句,而fetchone()方法则检索回一行数据,该数据作为元组处理。对于这里的语句,该元组包含一个单一的值,这个值将被该脚本打印出来。如果无法取得数据行,fetchone()将返回None值;这种情况的处理我们将在后面加以介绍。游标对象可用于发出多个语句,但是server_version.py获取版本字符串之后就不再需要该游标了,所以我们将其关闭即可。

最后,该脚本调用连接对象的close()方法关闭到服务器的连接,如下所示:

  1. conn.close ()

复制代码

之后,conn将被废弃,所以就无法再使用它来访问服务器了。

若要执行server_version.py脚本,可以从命令行调用Python,并给出该脚本的名称即可。之后,我们将看到类似如下所示的输出:

  1. % python server_version.py
  2. server version: 5.1.12-log

复制代码

这表明该MySQL服务器的版本是5.1.12;后缀-log说明查询日志记录功能已被启用。当然,实际上您可能会看到其他后缀,这要视您使用的数据库服务器而定。举例来说,后缀-beta表示该发行版本的稳定级别;如果您启用了调试功能,您就会在运行结果中看到-debug后缀。

所以可以建立脚本,这样可以通过其名称来运行之,而无需显式调用Python。在UNIX操作系统之下,可以为脚本开头部分添加一个#!行,用以规定Python解释器的完整路径。这样的话,系统就知道应使用哪个程序来执行此脚本。举例来说,如果Python解释器位于/usr/bin/python,那么可在脚本的第一行添加如下所示的内容:

  1. #!/usr/bin/python

复制代码

然后,使用chmod把该脚本变成可执行文件,这样我们就能通过下列命令来直接运行脚本:

  1. % chmod +x server_version.py
  2. % ./server_version.py

复制代码

其中,先导的./的作用是显式告诉命令解释程序:该脚本位于当前目录中,这样做是因为许多UNIX操作系统的帐户没有配置成在当前目录搜索命令。

在Windows操作系统中,无需#!行开头,不过即使加了这一行也没有关系,所以如果要把运行在UNIX系统的脚本移植到Windows系统的时候,无需对它进行修改。不过,与使用chmod将.py脚本变为可执行文件不同,我们需要把以.py结尾的文件与Python关联在一起,为此,可以在控制面板中打开“工具”菜单下的“文件夹选项”,单击文件类型选项卡,在此,将以.py作为扩展名的文件设置成使用Python打开。这样一来,我们就可以通过脚本的名称来调用它了,如下所示:

  1. C:\> server_version.py

复制代码

如果在 Windows 上安装的是ActiveState Python,那么ActiveState的安装程序会在安装过程中自动完成此关联过程。

四、更复杂的DB-API脚本

前面的示例脚本Server_version.py有许多缺点,比如,它没有捕捉异常,也不能在出错时指出到底是什么样的错误,此外,它也不允许执行语句时不返回任何结果。下面通过一个更加“高级”的脚本animal.py来演示如何解决这些问题,这里给出完整的animal.py代码。

  1. #animal.py:创建动物表并从中检索信息
  2. import sys
  3. import MySQLdb
  4. #连接到MySQL服务器
  5. try:
  6. conn = MySQLdb.connect (host = “localhost”,
  7. user = “testuser”,
  8. passwd = “testpass”,
  9. db = “test”)
  10. except MySQLdb.Error, e:
  11. print “Error %d: %s” % (e.args[0], e.args[1])
  12. sys.exit (1)
  13. #创建动物表并填充内容
  14. try:
  15. cursor = conn.cursor ()
  16. cursor.execute (“DROP TABLE IF EXISTS animal”)
  17. cursor.execute (“””
  18. CREATE TABLE animal
  19. (
  20. name CHAR(40),
  21. category CHAR(40)
  22. )
  23. “””)
  24. cursor.execute (“””
  25. INSERT INTO animal (name, category)
  26. VALUES
  27. (‘snake’, ‘reptile’),
  28. (‘frog’, ‘amphibian’),
  29. (‘tuna’, ‘fish’),
  30. (‘racoon’, ‘mammal’)
  31. “””)
  32. print “Number of rows inserted: %d” % cursor.rowcount
  33. #使用fetchone()进行循环提取
  34. cursor.execute (“SELECT name, category FROM animal”)
  35. while (1):
  36. row = cursor.fetchone ()
  37. if row == None:
  38. break
  39. print “%s, %s” % (row[0], row[1])
  40. print “Number of rows returned: %d” % cursor.rowcount
  41. #使用fetchall()进行循环提取
  42. cursor.execute (“SELECT name, category FROM animal”)
  43. rows = cursor.fetchall ()
  44. for row in rows:
  45. print “%s, %s” % (row[0], row[1])
  46. print “Number of rows returned: %d” % cursor.rowcount
  47. #发出修改名称的语句,共两种方式,第一种是在语句字符串
  48. #中使用数据值的字面值,第二种是使用位置标识符
  49. cursor.execute (“””
  50. UPDATE animal SET name = ‘turtle’
  51. WHERE name = ‘snake’
  52. “””)
  53. print “Number of rows updated: %d” % cursor.rowcount
  54. cursor.execute (“””
  55. UPDATE animal SET name = %s
  56. WHERE name = %s
  57. “””, (“snake”, “turtle”))
  58. print “Number of rows updated: %d” % cursor.rowcount
  59. #创建一个字典游标,这样就可以使用位置而非名称来访问数据列中的值了。
  60. cursor.close ()
  61. cursor = conn.cursor (MySQLdb.cursors.DictCursor)
  62. cursor.execute (“SELECT name, category FROM animal”)
  63. result_set = cursor.fetchall ()
  64. for row in result_set:
  65. print “%s, %s” % (row[“name”], row[“category”])
  66. print “Number of rows returned: %d” % cursor.rowcount
  67. cursor.close ()
  68. except MySQLdb.Error, e:
  69. print “Error %d: %s” % (e.args[0], e.args[1])
  70. sys.exit (1)
  71. conn.commit ()
  72. conn.close ()

复制代码

该脚本使用一个表来存储动物的名称和类别:

  1. CREATE TABLE animal
  2. (
  3. name CHAR(40),
  4. category CHAR(40)
  5. )

复制代码

本例中的表和一些语句取自于PEAR DB的说明文档。脚本animal.py的开头部分如下所示,从#!行可以看出它是打算运行于UNIX系统的:

  1. #!/usr/bin/python
  2. # animal.py – 创建动物表并从中检索信息
  3. import sys
  4. import MySQLdb

复制代码

如同Server_version.py一样,这个脚本也导入了MySQLdb,此外,它还导入了sys模块以供错误处理之用。如果发生错误,animal.py就会利用sys.exit()返回1来表明脚本异常结束。

下面介绍错误处理。导入必要的模块之后,animal.py使用connect()调用建立到服务器的连接。为了处理连接故障,比如,显示故障的原因,该脚本必须捕捉异常。在Python语言中,为了处理异常,必须把代码放到try语句中,并且包括一个except子句来包含错误处理代码。生成的连接的代码如下所示:

  1. try:
  2. conn = MySQLdb.connect (host = “localhost”,
  3. user = “testuser”,
  4. passwd = “testpass”,
  5. db = “test”)
  6. except MySQLdb.Error, e:
  7. print “Error %d: %s” % (e.args[0], e.args[1])
  8. sys.exit (1)

复制代码

Except子句中的异常类MySQLdb.Error用于取得MySQLdb可以提供的具体数据库错误信息,而变量e则用来存放错误信息。如果出现异常,MySQLdb会把有关信息存入e.args——这是一个包含错误代码和描述该错误的字符串组成的双元素元组。在本例中,except子句会打印这些值,然后退出。

任何数据库有关的语句都可以放入类似的try/except结构中来收集和报告错误,为了简便起见,后面的讨论中不再显示异常处理代码。

接下来介绍用于发送语句的方法。Animal.py接下来的代码将创建一个游标对象,并利用它发送设置和填充动物表的语句。这部分代码如下所示:

  1. cursor = conn.cursor ()
  2. cursor.execute (“DROP TABLE IF EXISTS animal”)
  3. cursor.execute (“””
  4. CREATE TABLE animal
  5. (
  6. name CHAR(40),
  7. category CHAR(40)
  8. )
  9. “””)
  10. cursor.execute (“””
  11. INSERT INTO animal (name, category)
  12. VALUES
  13. (‘snake’, ‘reptile’),
  14. (‘frog’, ‘amphibian’),
  15. (‘tuna’, ‘fish’),
  16. (‘racoon’, ‘mammal’)
  17. “””)
  18. print “Number of rows inserted: %d” % cursor.rowcount

复制代码

需要注意的是,这里的代码没有包含错误检查。请记住,它们可以放入try语句,这样一旦出错就会触发异常,继而被except子句所捕获并处理。但是考虑到代码的可读性,我们这里只给出了代码的主干部分。上面的语句将完成以下动作:

  • 动物表已经存在,则丢弃它。
  • 创建动物表。
  • 向表中插入一些数据,并报告添加的行数。

这些语句都是通过调用游标对象的execute()方法发出的。其中前两个execute()语句不会生成数据,但是第三个语句将生成一个表示已经插入行数的统计量。这个统计数字存放在游标的rowcount属性中。有些数据库接口是通过execution运行调用的返回值来提供这个统计数字的,但是 DB-API却不是这样。

此动物表已经建好,所以我们可以发出选择指令来从中检索信息了。如同前面的语句一样,SELECT语句也要使用execute()方法发出。然而,与DROP或者INSERT语句不同的是,SELECT语句会生成一个结果集合,也就是说,execute()仅仅发出语句,但是却没有返回结果集合。我们可以使用fetchone()方法每次返回一行数据,或者使用fetchall()方法一次全部搞定。在 animal.py中,这两者方法都用到了。下面是如何使用fetchone()每次检索一行数据的方法:

  1. cursor.execute (“SELECT name, category FROM animal”)
  2. while (1):
  3. row = cursor.fetchone ()
  4. if row == None:
  5. break
  6. print “%s, %s” % (row[0], row[1])
  7. print “Number of rows returned: %d” % cursor.rowcount

复制代码

方法fetchone()会把结果集合的下一行作为一个元组返回,如果已经到了结果集合的末尾的话,就返回值None。这里的循环会进行相应的检查,并在取尽结果集合的时候退出。对于返回的每一行,元组包含了两个值,Print语句会打印输出每个元组元素。然而,因为它们是安装在元组中的顺序使用的,所以Print语句正好可以如下编写:

  1. print “%s, %s” % row

复制代码

显示此语句结果之后,脚本还会显示返回的行数,即rowcount属性的值。

Fetchall()可以把整个结果集合作为以元组为元素的元组一次返回,或者,如果结果集合为空则会返回一个空的元组。为了访问个别数据行,可以枚举fetchall()返回的行集合,具体代码如下所示:

  1. cursor.execute (“SELECT name, category FROM animal”)
  2. rows = cursor.fetchall ()
  3. for row in rows:
  4. print “%s, %s” % (row[0], row[1])
  5. print “Number of rows returned: %d” % cursor.rowcount

复制代码

上面通过访问rowcount来打印行数。当fetchall()的时候,另一种确定行数的方法是使用它返回的值的长度,如下所示:

  1. print “%d rows were returned” % len (rows)

复制代码

前面的循环语句中是把数据行作为元组看待的,如果我们将其作为字典处理的话,就可以通过名字来访问各列的值了。以下代码展示了这一过程。需要注意的是,访问字典需要一种不同的游标,所以这里关闭了原来的游标,并利用一个不同的游标类获得了一个新游标,如下所示:

  1. print “%d rows were returned” % len (rows)

复制代码

结果集合中的空值将作为None返回。MySQLdb支持位置标识符功能,利用它可以在语句字符串之内为数据值绑定特殊标记。这就为直接向语句中嵌入值提供了替代方法。位置标识符机制会给数据值添加引号,并逸出出现在这些值内的所有的特殊字符。下面的示例演示了一个把蛇改成海龟的UPDATE语句,首先使用的是字面值,然后使用的是位置标识符。其中使用字面值的语句看上去是这样的:

  1. cursor.execute (“””
  2. UPDATE animal SET name = ‘turtle’
  3. WHERE name = ‘snake’
  4. “””)
  5. print “Number of rows updated: %d” % cursor.rowcount

复制代码

另外,我们还可以使用位置标识符标记%s,并为其捆绑相应的值的方式来发送这个语句,代码如下所示:

  1. cursor.execute (“””
  2. UPDATE animal SET name = %s
  3. WHERE name = %s
  4. “””, (“snake”, “turtle”))
  5. print “Number of rows updated: %d” % cursor.rowcount

复制代码

这里是上面的execute()调用方式的注意事项:

  • 每个位置标识符%s对应于一个插入该语句字符串的值。
  • 指示符%s不应该用引号括住,MySQLdb会根据需要添加引号。
  • 绑定给位置标识符的值放在一个元组中,它们在元组中的顺序就是出现在语句中的顺序。如果只有一个值x的话,可以写作(x,),表示这是个单元素元组。
  • 可以给位置标识符绑定一个None值,这表示向该语句中插入一个SQL空值。

发出有关语句之后,animal.py将关闭此游标,确认修改,并断开跟服务器的连接,如下所示:

  1. cursor.execute (“””
  2. UPDATE animal SET name = %s
  3. WHERE name = %s
  4. “””, (“snake”, “turtle”))
  5. print “Number of rows updated: %d” % cursor.rowcount

复制代码

连接对象的commit()方法确保当前事务中的修改已经在数据库中完成。在DB-API中,autocommit模式被禁用了,所以在断开连接之前必须调用commit(),否则所作修改可能会丢失。

如果动物表是一个MyISAM表,commit()就不起作用了:MyISAM是一种非事务性存储引擎,所以对MyISAM表的修改会立即生效,与 autocommit模式无关。如果使用了事务性存储引擎,当断开连接的时候如果不调用commit()会导致一个隐式的事务回滚。举例来说,如果我们在 CREATE TABLE语句后面加上ENGINE = InnoDB,并删除脚本末尾处的commit()调用,就会发现脚本运行后animal为空。

对于仅仅检索数据的脚本,由于不用对修改进行确认,所以commit()对它们来说也是不需要的。

五、关于移植事项

如果希望把基于MySQLdb的DB-API脚本移植到不同的数据库,则要需要修改的地方主要是出现驱动程序名称的地方:

1. 用于导入驱动模块的import语句,必须进行修改以便导入相应的驱动程序。

2. 连接数据库服务器的connect()调用。此connect()方法是通过驱动模块的名称进行访问的,所以驱动程序名称需要更改。此外,connect()参数语法也会随着驱动程序的不同而变化。

3. 异常处理。在except子句中的异常类也是通过驱动程序名称引用的,所以也要根据需要进行相应的修改。

进行移植时,除了驱动程序名称之外还有一些地方要注意,那就是位置标识符。DB-API规范允许使用某些位置标识符语法,但是一些驱动程序使用的语法确不同于MySQLdb支持的语法。

六、小结

本文向读者详细介绍如何利用DB-API模块方便地开发MySQL脚本。我们首先介绍了有关软件的安装方法,随后分别用两个实例对DB-API模块做了深入的介绍,最后讨论了用Python DB-API编写的MySQL脚本的移植注意事项。希望本文能够对您有所帮助。

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注