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

版权声明:本文为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储存并查找数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容