Head First Python

《Head First Python》Ch7:使用数据库

2021-02-18  本文已影响0人  老A不加V

一、安装MySQL

MySQL Community Downloads
windows10上安装mysql(详细步骤)

二、为Python安装一个MySQL数据库驱动程序

MySQL-Connector/Python无法用pip安装,手动安装MySQL-Connector/Python驱动程序:

MySQL-Connector/Python

选择源码(Source Code)

在页面最下方找到平台独立(Platform Independent)的压缩包

(Windows下载ZIP文件,Linux和Mac OS可下载GZ)

下载解压为文件夹mysql-connector-python-8.0.12,作为管理员打开终端窗口,需要在mysql-connector-python-8.0.12中执行以下命令:

py -3 setup.py install

Linux或Mac OS,则使用以下命令:

sudo -H python3 setup.py install

建议环境PATH配置修改!

三.创建Web应用的数据库和表

使用数据库 具体使用Python的DB-API (如何解决error2003,error1064,error1045)

启动MySQL控制台,

要作为MySQL数据库管理员登录(使用根目录ID

注意:

在安装mysql5.7版本时,经常会遇到mysql -u root -p直接回车登陆不上的情况,原因在于5.7版本在安装时自动给了一个随机密码,坑爹的是在init步骤的时候不像linux系统会给出命令行提示,需要手动在mysql目录下搜索*.err,以文本形式打开才能看到如下内容:

016-02-25T15:09:43.033062Z 1 [Note] A temporary password is generated for root@localhost:>mso<k70mrWe

红色字母即为第一次的登陆密码,记得加双引号。

下面是启动控制台的命令:

mysql -u root -p

创建一个数据库,名为vsearchlogDB

以下为创建数据库的命令:

mysql> create database vsearchlogDB;

参考head first python创建新的用户和口令失败,如下

mysql> grant all on vsearchlogDB.* to 'vsearch' identified by 'vsearchpasswd';

于是出现错误error1064:

解决方法:create user语句创建一个用户,然后再grant权限,成功。

mysql> create user vsearch identified by 'vsearchpasswd';

mysql> grant all on vsearchlogDB.* to 'vsearch';

推测是mysql8以后不允许直接建立用户并给权限的操作了。

而后用新创建的vsearch用户登录:

mysql -u vsearch -p vsearchlogDB

却又出现错误error1045:

解决方案:mysql -u root -p 直接回车两次

use +数据库名,即可

然后就可以存放数据:

可以看到log表已经存在,而且结构符合这个Web应用的日志记录需要,输入quit退出控制台。

Python中的BD-API

       在没有DB-API之前,接口程序混乱。具体的就是说,由于最底层用的数据库技术不同,所以在应用程序层就要针对特定的数据库进行特定的编码,如果要改变一个版本所使用的底层数据库,那么之前编写的应用程序中关于数据库的代码也要进行相应的改变。

python DB-API:python访问数据库的统一接口规范。

       这里有一个约定:程序员使用python与任何底层数据库交互时都使用DB-API,而不论这个数据库技术具体是什么,这么做是因为,利用驱动程序,程序员无需了解与数据库具体API交互的底层细节,因为DB-API在代码与驱动程序之间提供了一个抽象层,这里的想法是:通过使用DB-API编程,可以根据需要替换掉底层数据库技术,而无须丢弃现有的代码。

1、定义连接属性

连接到MySQL服务器时需要4部分信息:

(1)运行MySQL服务器的计算机(称为主机)的IP地址/主机名;

(2)要使用的用户ID;

(3)与用户ID关联的口令;

(4)这一用户ID想要交互的数据库名。

>>> dbconfig = {'host':  '127.0.0.1',               

  #服务器在我们本地计算机上运行。所以“host”使用localhost IP地址。

'user':  'vsearch',

#这一章前面创建的“vsearch”用户ID赋给“user”键。

'password':  'vseacrh',

“password”键赋为我们的用户ID相应的正确口令。

'database':  'vsearchlogDB', }

数据库名(这里就是“vsearchlogDB”)赋至“database”键。

2、导入数据库驱动程序

>>> import mysql.connector

3、建立与MySQL服务器的一个连接

conn = mysql.connector.connect(**dbconfig)

(**)记法告诉connect函数用一个变量提供了参数字典,如果看到这种记法,connect函数会把这个字典展开为4个单独的参数,然后在connect函数中使用这些参数来建立连接。

4、打开一个游标

要向数据库发送SQL命令(通过刚才打开的连接)以及从数据库接收结果,我们需要一个游标,可以把游标理解成数据库中文件的句柄。

创建游标很简单:只需要调用cursor方法,每个连接对象都有这个方法。

接下来需要一个翻译机,也就是游标(cursor),代码如下:

>>> cursor=conn.cursor()

然后就可以用这个游标,使用SQL语言与服务器端的数据库通信了。

前面我们所做的这些操作,在无论使用什么数据库时,都是通用的。只是连接属性可能不同而已。

5、完成SQL查询

可以利用cursor变量向MySQL发送SQL查询,以及获取MySQL处理查询生成的结果,我认为的是这个跟ajax中的request请求对象很像。

_SQL = """show tables"""

cursor.execute(_SQL);

一般用三重双引号来编写要发送给数据库服务器的SQL,之所以使用三重双引号,是因为三重双引号可以暂定不启用python解释器中的“行末即语句结束的规则”。

执行cursor.execute()命令时,这个SQL查询会被发送到MySQL的服务器,它会执行这个查询(假设正确),不过结果并不会立即显示,必须要请求结果。

注意,SQL代码是存在一个字符串中的,这样python就不会试图去理解这句代码的意思,而是老老实实通过cursor把这句代码传到服务器端,并运行。

然而它并不会返回一个结果,需要我们自己去请求结果。

请求结果有三种方式:

fetchone:请求一行结果;

fetchmany:请求指定行结果;

feychall:请求所有结果。

我们选择请求所有结果。代码如下:

>>> res=cursor.fetchall()

>>> res

[('log', ) ]

这里要特别注意点是:fetchall()方法,它返回的是客户端缓冲区现在还没有被影响的记录。注意返回的是一个列表,列表中的元素是元组。也就是说。fetchall返回值的类型一定是一个元组列表,即使只有一个元素也不例外。由于我们在3.3中只建了一张名为log的表,因此这里只有log一个结果。

如果想要查看表中的内容怎么办呢?用SQL的describe语句。如下:

>>> _SQL="""describe log"""

>>> cursor.execute(_SQL)

>>> res=cursor.fetchall()

>>> res

[('id', b'int', 'NO', 'PRI', None, 'auto_increment'), ('ts', b'timestamp', 'YES', '', b'CURRENT_TIMESTAMP', 'DEFAULT_GENERATED'), ('phrase', b'varchar(128)', 'NO', '', None, ''), ('letters', b'varchar(32)', 'NO', '', None, ''), ('ip', b'varchar(16)', 'NO', '', None, ''), ('browser_string', b'varchar(256)', 'NO', '', None, ''), ('results', b'varchar(64)', 'NO', '', None, '')]

感觉有点乱。但还是可以看出,返回的仍是一个列表(最外面是方括号[]),列表的每个元素都是元组(次外层是括号()),元组元素为字符串,若元组中元素为空,则元组元素不显示。

表中每一行对应一个元组。这样来看,按行输出比较合适,效果如下:

>>> for row in res:

        print(row)

还不错。res中就保存了log表中的所有数据。

下面来看如何在表中添加数据。使用SQL的insert语句。如下:

>>> _SQL="""insert into log

        (phrase,letters,ip,browser_string,results)

        values

        ('hitch-hicker','aeiou','127.0.0.1','Firefox',"{'e','i'}")"""

>>> cursor.execute(_SQL)

       然而这种插入操作很蠢,为什么呢?观察_SQL这个变量,它是一句SQL代码,对于insert操作,每次都要重新生成一条代码来插入不同的数据,这太低效了,如果有一种方法,可以将insert语句与待插入内容分开,要用的时候再合并,也就是说,把insert语句看成一个函数,可重用,这就好了。有这种方法。代码如下:

>>> _SQL="""insert into log

        (phrase,letters,ip,browser_string,results)

        values

        (%s,%s,%s,%s,%s)"""

>>> cursor.execute(_SQL,('hitch-hiker','xyz','127.0.0.1','Safari','set()'))

       _SQL并没有具体说明要插入的内容,而是用五个占位符%s来进行指定。在实际应用时,只需要在execute后加入一个元组,元组内容对应占位符即可。

       然后查看表中内容是否添加成功。使用select命令。但很有可能select无法返回任何数据。因为,当使用insert操作时,由于写数据库是一个开销很大的操作,所以数据库系统会缓存insert,然后再一次性应用全部的insert。这带来的问题就是,刚刚插入的数据可能访问不到。但是可以通过事务提交(conn.commit())来强制数据库系统将所有可能缓存的数据全部提交到数据库表中,这样既可解决访问不到的问题。

注意describe是用于描述一个表的,它描述了表的属性,而不管表的内容;select则用于显示表的内容,而不显示表的属性。代码如下:

>>> conn.commit()

>>> _SQL="""select * from log"""

>>> cursor.execute(_SQL)

>>> for row in cursor.fetchall():

        print(row)

得到结果:

6、关闭游标和连接

数据提交到数据库表中后,要进行清理,关闭游标和连接。

>>> cursor.close()

True

>>> conn.close()

这是因为数据库系统是一组有限的资源,不关闭会导致资源严重浪费,其它用户无法访问。

四、创建代码处理Web应用的数据库和表

下面是回顾最近做的一个项目中关于数据库连接的源码:

#定义你的连接属性

dbconfig = {"host": "127.0.0.1",

"user": "vsearch",

"password": "vsearch",

"database": "vsearchlogDB",

}

#导入数据库驱动程序

import mysql.connector

#建立一个连接并创建一个游标

conn = mysql.connector.connect(**dbconfig)

cursor = conn.cursor()

#将查询赋至一个字符串(注意5个占位符参数)

_SQL = """insert into log

(phrase, letters, ip, browser_string, results)

values

(%s, %s, %s, %s, %s)"""

#将查询发送到服务器,记得要为各个参数提供值(以元组的形式)

cursor.execute(_SQL, ("afeng", "jingjing", "127.0.0.1", "opera", "{'x', 'y'}"))

#强制要求数据库写数据

conn.commit()

#从表获取(刚写入的)数据,逐行显示输出。

_SQL = """select * from log"""

cursor.execute(_SQL)

for row in cursor.fetchall():

     print(row)

#完成时进行清理

cursor.close()

conn.close()

由于有了以上的铺垫,我们现在要实现以下功能:修改Webapp的log_request函数,让它保存的数据不是存在一个txt里,而是存在SQL数据库中。

原代码如下:

deflog_request(req:'flask_request',res:str)->None:

    withopen('vsearch.log','a')aslog:

        print(req.form,req.remote_addr,req.user_agent,res,file=log,sep='|')

修改之后,要实现与数据库的连接,实现写入等功能,其实蛮简单,如下:

deflog_request(req:'flask_request',res:str)->None:

       dbconfig={'host':'127.0.0.1',

                       'user':'vsearch',

                       'password':'vsearchpasswd',

                      'database':'vsearchlogDB',}

    import mysql.connector

     conn=mysql.connector.connect(**dbconfig)

     cursor=conn.cursor()

    _SQL="""insert into log

               (phrase,letters,ip,browser_string,results)

                values

               (%s,%s,%s,%s,%s)"""

  cursor.execute(_SQL, (req.form['phrase'],

                                      req.form['letters'],

                                      req.remote_addr,

                                      req.user_agent.browser,

                                      res,))

     conn.commit()

     cursor.close()

     conn.close()

       有两点要注意,其一,是import的位置,不要在函数内部;其二,对于数据的写入,req.form是一个字典,有两项,一项是phrase,即输入;一项是letters,即输出。      

     另外,注意第四项写入的元素req.user_agent.browser,不再是整个浏览器串(存储在req.user_agent),我们只抽取出浏览器名。

应用新修改的vsearch4web.py:

C:\webapps> py -3 vsearch4web.py

现在我们发现了一个新的问题:

对于那些可能会用到多次的功能,如建立连接、断开连接等,是否可以实现重用呢?

记住“建立、处理、清理”模式!

如果每次修改SQL都需要这样繁琐的一步步建立和断开连接,一点都不优美。联想到之前的打开-关闭文件,我们也可以用with来实现这一点。如下所示:

with UseDatabase(dbconfig) as cursor:

这是使用with版本的代码。但是还不能工作,因为我们还没有写Usedatabase这一函数的上下文管理器,这里看看就好。

使用上下文管理器需要创建类,在下面几章,我们将学习如何创建类。

上一篇下一篇

猜你喜欢

热点阅读