美文网首页Head First Python
《Head First Python》Ch9:上下文管理协议

《Head First Python》Ch9:上下文管理协议

作者: 老A不加V | 来源:发表于2021-02-25 22:17 被阅读0次

    版权声明:本文为CSDN博主「一笑照夜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/erwugumo/article/details/95812270

    1、上下文管理的内容

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

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

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

    with f1 as a:
        f2
    

    后台实际运行如下:

    f1
    __enter__
    f2
    __exit__
    

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

    这里的变量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()
    

    有以下几点要注意:

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

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

    3. 一切需要多次使用的变量都要写入类的属性表,如何写入?在声明它的时候在前面加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储存并查找数据。

    相关文章

      网友评论

        本文标题:《Head First Python》Ch9:上下文管理协议

        本文链接:https://www.haomeiwen.com/subject/dklpfltx.html