Python学习笔记10:上下文协议
我们从一门语言转到另一门新语言,最先注意到的无疑是这门语言有没有什么类似独门绝技一样的东西,而今天要说的就是这么一种Python独有的特性:上下文协议。
基本概念
之前我们介绍文件的时候有提到过使用with/as
来实现自动打开与关闭文件,这样做可以避免开发者忘记关闭文件,无疑相当方便。
我们现在把眼光放高一点,从具体的开启、处理、关闭文件这个简单场景上升到这样一个模式:
- 在执行前进行一些准备活动。
- 执行一些行为。
- 在执行后进行一些收尾活动。
这个模式是不是具有一定的通用性?
比如数据库连接,在执行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
的匿名类填充业务逻辑,最后就可以实现上下文调用。