zoukankan      html  css  js  c++  java
  • 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/ 请尊重其他人的劳动成功,转载请注明。
  • 相关阅读:
    UVa OJ 148 Anagram checker (回文构词检测)
    UVa OJ 134 LoglanA Logical Language (Loglan逻辑语言)
    平面内两条线段的位置关系(相交)判定与交点求解
    UVa OJ 130 Roman Roulette (罗马轮盘赌)
    UVa OJ 135 No Rectangles (没有矩形)
    混合函数继承方式构造函数
    html5基础(第一天)
    js中substr,substring,indexOf,lastIndexOf,split等的用法
    css的textindent属性实现段落第一行缩进
    普通的css普通的描边字
  • 原文地址:https://www.cnblogs.com/Moon-Face/p/14551000.html
Copyright © 2011-2022 走看看