• Python学习笔记10:上下文协议


    Python学习笔记10:上下文协议

    我们从一门语言转到另一门新语言,最先注意到的无疑是这门语言有没有什么类似独门绝技一样的东西,而今天要说的就是这么一种Python独有的特性:上下文协议。

    基本概念

    之前我们介绍文件的时候有提到过使用with/as来实现自动打开与关闭文件,这样做可以避免开发者忘记关闭文件,无疑相当方便。

    我们现在把眼光放高一点,从具体的开启、处理、关闭文件这个简单场景上升到这样一个模式:

    1. 在执行前进行一些准备活动。
    2. 执行一些行为。
    3. 在执行后进行一些收尾活动。

    这个模式是不是具有一定的通用性?

    比如数据库连接,在执行SQL前我们要进行数据库连接并创建游标,在执行后要提交SQL并断开连接。

    如果我们能把这些行为抽象出来,进行一定的封装,就可以让开发者从频繁的准备活动或收尾活动中脱离开来,专注于业务代码,这无疑相当有用。

    而这就是Python中上下文协议的用途。

    在Python中,上下文协议的实现可以通过类来实现,只要定义了约定的方法就可以使用with语句进行上下文调用。

    Python的上下文协议实现很像Java中的接口,这个接口定义了两个必须要实现的方法。

    class ListReader():
        def __init__(self, aList: list):
            self.list = aList
    
        def __enter__(self) -> list:
            print("will print a list:")
            return self.list
    
        def __exit__(self, expType, expVal, expTrace):
            print("end")
    
    
    aList = [1, 2, 3, 4, 5, 6]
    with ListReader(aList) as lr:
        print(lr)
    

    输出

    will print a list:
    [1, 2, 3, 4, 5, 6]
    end

    上边的例子展示了一个很简单的实现了上下文协议的类ListReader,如示例所示,要想实现上下文协议,需要在自定义类中实现__enter____exit__方法,他们分别用于准备阶段和清理阶段。

    在这个例子中,在with语句执行的时候,解释器会先执行ListReader的构造函数初始化对象,然后再调用__enter__并返回一个list对象给lr,接着执行print(lr)输出这个列表,最后在with代码块执行完毕后,退出with语句的时候执行ListReader__exit__方法,进行扫尾工作。

    可能有人会疑惑为什么__enter__一个参数都没有,而__exit__有三个参数。

    这是因为执行上下文协议的场景通常是数据库操作或者文件操作,很容易产生异常,所以这三个参数都是异常产生时候的异常信息,可以在__enter__中根据出现的异常做不同处理。

    改进web应用

    在了解Python的上下文协议后,我们可以在之前的Web应用中应用上下文协议来改进数据库操作。

    之前我们对数据库操作是封装了一个类,在执行SQL的时候直接调用以下方法:

        def executeSQL(self, _SQL: str, params: tuple) -> list:
            """执行SQL"""
            self.connect()
            self.cursor.execute(_SQL, params)
            results = self.cursor.fetchall()
            self.dbConnect.commit()
            self.close()
            return results
    

    这存在一些问题,比如你说每次调用都要重复连接、提交、断开数据库操作,这存在一些资源浪费,比如在批量执行写入或读取的时候,这样效率很低。

    现在我们使用上下文协议来新建一个数据库操作封装:

    import mysql.connector
    class MyDB2():
        def __init__(self):
            self.dbconfig = {"host": "127.0.0.1", "user": "root",
                             "password": "", "database": "myweb"}
    
        def __connect(self):
            self.dbConnect = mysql.connector.connect(**self.dbconfig)
            self.cursor = self.dbConnect.cursor()
    
        def __close(self):
            self.dbConnect.commit()
            self.cursor.close()
            self.dbConnect.close()
    
        def __enter__(self) -> 'cursor':
            self.__connect()
            return self.cursor
    
        def __exit__(self, expType, expVal, expTrace):
            self.__close()
    

    然后使用上下文协议的方式调用:

    def writeLog(logInfo: dict) -> None:
        with MyDB2() as cursor:
            _SQL = '''INSERT INTO LOG (phrase,letters,ip,browser_string,results)
                    VALUES (%s,%s,%s,%s,%s)'''
            params = (logInfo['formData']['phrase'], logInfo['formData']
                      ['letters'], logInfo['userIp'], logInfo['userAgent'], logInfo['results'])
            cursor.execute(_SQL, params)
    
    
    def getLog() -> list:
        lines = []
        results = []
        with MyDB2() as cursor:
            _SQL = '''SELECT * FROM LOG'''
            cursor.execute(_SQL)
            results = cursor.fetchall()
        for logInfo in results:
            lines.append(
                [logInfo[4], logInfo[3], logInfo[1], logInfo[2], logInfo[5]])
        return lines
    

    可能这个例子中并不能显示这样改带来的好处,但如果遇到需要批量执行SQL的时候就能显出性能差异。当然我们要清醒地认识到这样改变后不是每次执行SQL立即生效,所以with语句块中的SQL不能相互冲突,比如后一步的查询需要依赖于前一步的变更或插入,如果那样你就不能放在同一个上下文中。

    修改以后的完整web应用代码已上传到百度盘:

    链接:https://pan.baidu.com/s/1vL3hUxnOEa89dqee_a8tZg
    提取码:wd70
    复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V1的分享

    用PHP实现上下文协议

    准确的来说,上下文协议本身是一种设计模式的思想,不过python提供了语言级别的支持,让写法显得很简洁。我们同样可以尝试在其它语言中实现这一设计模式。

    在这个例子中我使用PHP来实现一个上下文模式协议:

    我们先建立一个上下文协议接口ContextInterface.php

    <?php
    interface ContextInterface{
        /**
         * 上下文准备环节
         * @return Object
         */
        public function enter();
        /**
         * 上下文清理环节
         */
        public function exit();
    }
    

    再建立一个数据库实现MyDB.php,并实现上下文接口,以起到自动切换上下文的功能。

    <?php
    require_once(".\ContextInterface.php");
    class MyDB implements ContextInterface
    {
        private $dbConnect;
        private $dbConfig;
        function __construct()
        {
            $this->dbConfig = array(
                'db' => 'myweb',
                'servername' => '127.0.0.1',
                'username' => 'root',
                'password' => ''
            );
        }
        public function enter()
        {
            $this->dbConnect = mysqli_connect($this->dbConfig['servername'],
                                                 $this->dbConfig['username'], 
                                                 $this->dbConfig['password'],
                                                $this->dbConfig['db']);
            // mysql_select_db($this->dbConfig['db'], $this->dbConnect);
            return $this->dbConnect;
        }
        public function exit()
        {
            mysqli_close($this->dbConnect);
        }
    }
    

    最后再实现一个抽象类ContextCallable.php,用于实现自动切换上下文的功能,具体的业务逻辑可以通过实现相应的抽象方法来实现。

    <?php
    require_once ".\ContextInterface.php";
    abstract class ContextCallable
    {
        /**
         * @param ContextInterface $contextInterface
         */
        private $contextInterface;
        function __construct(ContextInterface $contextInterface)
        {
            $this->contextInterface = $contextInterface;
        }
        public function run()
        {
            $callBack = $this->contextInterface->enter();
            $this->action($callBack);
            $this->contextInterface->exit();
        }
        /**
         * 实现上下文中的业务逻辑
         * @param object $callBack 上下文协议接口返回的句柄
         */
        abstract protected function action($callBack);
    }
    

    我们现在测试一下这个上下文协议:

    <?php
    require_once ".\ContextCallable.php";
    require_once ".\MyDB.php";
    $mydb = new MyDB();
    $sqlExcuter = new class($mydb) extends ContextCallable
    {
        protected function action($callBack)
        {
            $dbConn = $callBack;
            $SQL = "SELECT * FROM log";
            $result = mysqli_query($dbConn, $SQL);
            $logs = mysqli_fetch_all($result, MYSQLI_ASSOC);
            mysqli_free_result($result);
            print_r($logs);
        }
    };
    $sqlExcuter->run();
    

    这其中通过创建一个继承自Contextcallable的匿名类填充业务逻辑,最后就可以实现上下文调用。

    • PHP的mysqli调用可以参考这里
    • PHP中的匿名类使用可以参考这里
    本篇文章首发自魔芋红茶的博客https://www.cnblogs.com/Moon-Face/ 请尊重其他人的劳动成功,转载请注明。
  • 相关阅读:
    axb_2019_fmt32 盲打和格式化字符串
    ciscn_2019_final_3 需要避开当前free的chunk的下一个chunk不能超过arena的边界
    xdctf2015_pwn200
    valarray类
    Mysql 常用命令.
    如何处理IO
    等号两边自动添加括号
    Request JSON
    开机小脚本自动打开sublime text 和git-bash
    git 同步勾子
  • 原文地址:https://www.cnblogs.com/Moon-Face/p/14551000.html
Copyright © 2020-2023  润新知