【跟着Head First学python】9、上下文管理协议:挂接Python的with语句

1、上下文管理的内容

在我初步的认知里,上下文管理,就是with,用处就是;为了减少代码量,提高代码可读性,同时避免犯一些缺少exit的错误。当然不止这些,但现在,我们只需要知道这些就已经足够。

上下文管理,首先需要一个类,这个类里面必须有两个方法:__enter__和__exit__。当一个类中含有这两个方法,那么解释器就认为这个类是一个上下文管理器,它遵循上下文管理协议。也就是说,上下文管理器是一类类。

当程序运行到含有with的一行时:

with f1 as a:
    f2

后台实际运行如下:

f1
__enter__
f2
__exit__

这是一般的情形。在实际使用中,可以缺少f1,可以缺少as a,但是__enter__和__exit__是一定不能缺少的。

这里的变量a是由__enter__这个函数的返回值确定的。

举例如下,我们建立一个类:

class HaveATry:
    def __init__(self)->None:
        print('1')
    def __enter__(self)->None:
        print('2')
    def __exit__(self,a,b,c)->None:
        print('3')

很简单,在创建一个对象时打印1,在执行enter时打印2,在执行exit时打印3。注意一个细节,none必须写作None,全小写会报错。

注意一点,前面两个函数的参数可以只有self,但exit必须有四个参数:self、exc_type、exc_value、exc_trace。后面三个是用于异常处理的,暂时不用去管。

当我们执行with,有init部分时,如下:

印证了上面的顺序。

如果不用init,那么为了让with知道它在管理谁的的上下文,我们需要先建立一个对象,然后with这个对象即可,如下:

这样我们就明白了with背后发生了什么。

2、用上下文管理改写webapp的log_request函数

我们先看原来的代码:

import mysql.connector
def log_request(req:'flask_request',res:str)->None:
    dbconfig={'host':'127.0.0.1',
              'user':'vsearch',
              'password':'vsearchpasswd',
              'database':'vsearchlogDB',}
    conn=mysql.connector.connect(**dbconfig)
    cursor=conn.cursor()
    _INSERT="""insert into log
	           (phrase,letters,ip,browser_string,results)
	           values
	           (%s,%s,%s,%s,%s)"""
    cursor.execute(_INSERT,(req.form['phrase'],
                            req.form['letters'],
                            req.remote_addr,
                            req.user_agent.browser,
                            res,))
    conn.commit()
    cursor.close()
    conn.close()

可以分成四部分:

第一部分初始化:

dbconfig={'host':'127.0.0.1',
          'user':'vsearch',
          'password':'vsearchpasswd',
          'database':'vsearchlogDB',}

用于配置建立连接所需的设定。

第二部分建立连接:

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

用于客户端和服务器建立连接,并生成一个游标。

第三部分实现函数功能:

 _INSERT="""insert into log
            (phrase,letters,ip,browser_string,results)
            values
            (%s,%s,%s,%s,%s)"""
cursor.execute(_INSERT,(req.form['phrase'],
                        req.form['letters'],
                        req.remote_addr,
                        req.user_agent.browser,
                        res,))

第四部分断开连接:

conn.commit()
cursor.close()
conn.close()

接下来,我们要将原代码改写成一个上下文管理器,将上面几部分有选择性的写入类中。

我们将类起名为UseDatabase。代码如下:

import mysql.connector

class UseDatabase:
    def __init__(self,dbconfig:dict)->None:
        self.dbconfig=dbconfig

    def __enter__(self)->'cursor':
        self.conn=mysql.connector.connect(**self.dbconfig)
        self.cursor=self.conn.cursor()
        return self.cursor

    def __exit__(self,exc_type,exc_value,exc_trace)->None:
        self.conn.commit()
        self.cursor.close()
        self.conn.close()

有以下几点要注意:

第一个细节,初始化函数中,有一个参数是配置字典,它的类型应该为dict,要记住。

第二个,__enter__函数的返回类型是cursor,但不能把cursor直接写上去,而是要用单引号括起来然后写上去,因为cursor是一个变量,解释器看了会犯迷糊。

第三个,一切需要多次使用的变量都要写入类的属性表,如何写入?在声明它的时候在前面加self即可。如dbconfig在__init__中赋值后,在__enter__中再次使用;conn和cursor也是一样。因此,它们前面都要加self。

这样,就写好了我们的上下文管理器。可以按如下形式使用它:

可以看到,已经正确连接。

这样一来,我们在与数据库交互时,只需要配置初始化条件、写下需要的SQL代码,连接与断开都不需要去关注了。多么美好。

3、用上下文管理器改写webapp中的view_the_log函数

在之前,我们的view_the_log函数如下:

def view_the_log()->str:
    contents=[]
    with open('vsearch.log') as log:#with 不具有循环功能
        for line in log:
            contents.append([])
            for item in line.split('|'):
                contents[-1].append(escape(item))
    #return str(contents)
    titles=('Form Data','Remote_addr','User_agent','Results')
    return render_template('viewlog.html',
                           the_title='View Log',
                           the_row_titles=titles,
                           the_data=contents,)

当时我们的数据处理过程是这样的:从request中选择需要的数据,以用‘|’分隔开的形式把数据存在一个txt文件中,读取的时候也从这个txt文件中读取,根据‘|’来分隔不同部分。现在,我们的数据存在SQL中,SQL本身即是结构化存储数据,因此不再需要我们去处理数据了,直接读取即可。

在改写之前,先学习一条SQL语句:

select phrase,letters,ip,browser_string,results from log

这是一句查询语句,从名为log的表中查找名为phrase,letters,ip,browser_string,results的列。

改写函数如下:

def view_the_log()->str:
    dbconfig={'host':'127.0.0.1',
              'user':'vsearch',
	      'password':'vsearchpasswd',
	      'database':'vsearchlogDB',}
    #contents=[]
    with UseDatabase(dbconfig) as cursor:
        _SELECT="""select phrase,letters,ip,browser_string,results from log"""
        cursor.execute(_SELECT)
        contents=cursor.fetchall()
    titles=('Phrase','Letters','Remote_addr','User_agent','Results')
    return render_template('viewlog.html',
                           the_title='View Log',
                           the_row_titles=titles,
                           the_data=contents,)  

有以下几点要注意:

首先,上述代码是一整个函数。也就是说,作用域是所有代码。因此,不需要在with前先声明contents变量,最开始我这么写是怕with部分结束后contents会被销毁,实际上,with部分不是一个独立的作用域,因此不用这么做。

其次,执行_SELECT这句代码并不会返回一个列表什么的,需要我们自己用fetchall函数去取得这个列表。

最后,虽然我们的显示列表由四列变成了五列,但是并不需要更改html代码,它适用于任意数量行的表格。

现在我们注意到了一个问题,那就是dbconfig部分我们用了两次,能不能在函数外声明,让它只需要创建一次,然后多次调用呢?

当然可以。只是我们最好不要直接在外面建立这个字典。而是将其写入Flask自带的配置字典app.config中。代码如下:

app.config['dbconfig']={'host':'127.0.0.1',
                        'user':'vsearch',
                        'password':'vsearchpasswd',
                        'database':'vsearchlogDB',}

这是一个字典的字典,若我们想在函数中调用dbconfig字典,只需调用aopp.config['dbconfig']即可。如下:

app.config['dbconfig']={'host':'127.0.0.1',
                        'user':'vsearch',
                        'password':'vsearchpasswd',
                        'database':'vsearchlogDB',}


def log_request(req:'flask_request',res:str)->None:
    #with open('vsearch.log','a') as log:
        #print(req.form,req.remote_addr,req.user_agent,res,file=log,sep='|')
    with UseDatabase(app.config['dbconfig']) as cursor:
        _INSERT="""insert into log
                (phrase,letters,ip,browser_string,results)
                values
                (%s,%s,%s,%s,%s)"""
        cursor.execute(_INSERT,(req.form['phrase'],
                                req.form['letters'],
                                req.remote_addr,
                                req.user_agent.browser,
                                res,))

@app.route('/viewlog')
def view_the_log()->str:
    #contents=[]
    with UseDatabase(app.config['dbconfig']) as cursor:
        _SELECT="""select phrase,letters,ip,browser_string,results from log"""
        cursor.execute(_SELECT)
        contents=cursor.fetchall()
    titles=('Phrase','Letters','Remote_addr','User_agent','Results')
    return render_template('viewlog.html',
                           the_title='View Log',
                           the_row_titles=titles,
                           the_data=contents,)  

这样就实现了webapp使用SQL储存并查找数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值