1.with以及上下文管理的解析
with open('test.txt', 'r') as f:
f.write('hello')
with语句的作用实际上就是对其后的代码求值(本例中即为调用open函数)。该表达式返回一个对象,该对象包含两个特殊方法: __ enter __和 __ exit __ ,__enter__方法返回的结果会赋给as关键字后面的变量。这里要注意,with后的表达式的结果没有赋给所谓的变量,实际上返回值没有赋给任何对象,只有__enter__的返回值会被赋给该变量。
简单示例
class ContextManager(object):
def __init__(self):
self.entered = '未使用上下文管理器'
def __enter__(self):
self.entered = '进入上下文管理器'
return self
def __exit__(self, exc_type, exc_instance, traceback):
self.entered = '退出上下文管理器'
with如何工作:
Python对with的处理很聪明。基本思想是with所求值的对象必须有一个enter()方法,一个exit()方法。
紧跟with后面的语句被求值后,返回对象的enter()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的exit()方法。在with后面的代码块抛出任何异常时,exit()方法被执行。正如例子所示,异常抛出时,与之关联的type,value和stack trace传给exit()方法,因此抛出的ZeroDivisionError异常被打印出来了。开发库时,清理资源,关闭文件等等操作,都可以放在exit方法当中。
在python shell中执行以下操作
>>> cm = ContextManager() # 这里只创建一个ContextManager的实例
>>> cm.entered # 查看实例的entered值
'未使用上下文管理器'
# 如果使用ContextManager为上下文管理器
>>> with ContextManager() as cm:
cm.entered
'进入上下文管理器'
>>> cm.entered
'退出上下文管理器'
可以看出,with语句是进入上下文管理器的入口且默认执行__enter__函数,而退出上下文管理器时默认执行__exit__函数。
小结
上下文管理器提供了确保资源被正确处理的优秀方式,并且能够将需要在程序中多个不同位置重复使用的异常代码封装到一个位置。
与装饰器一样,上下文管理器适用于采纳“只做一次”原则的工具,除非迫不得已需要在多处重复代码。装饰器用于封装命名函数与类,而上下文管理器更适用于封装任意代码段。
2.使用上下文管理器连接数据库
打开和关闭资源(如文件和数据库连接还有异常处理)是编写上下文管理器的重要应用。确保出现异常时正确关闭资源往往很重要,这样能够避免最终随着时间的推移而产生很多的僵尸进程。
上下文管理器的优势在于此。通过在__enter__方法中打开资源并返回它,可以保证__exit__方法能执行,同时也能对异常进行处理。
下面是一个连接MySql数据库的上下文管理器:
import pymysql
from my_test_httprunner import setting
class DB():
#在setting.databases我已创建了数据库链接相关信息,在下方调用时直接写语句即可
def __init__(self, host=setting.databases["HOST"],user=setting.databases["USERNAME"],
passwd=setting.databases["PASSWORD"],db=setting.databases["DATABASE"]):
# 建立连接
self.conn = pymysql.connect(host=host, db=db, user=user, passwd=passwd)
# 创建游标
self.cur = self.conn.cursor(cursor = pymysql.cursors.Cursor)
def __enter__(self):
# 返回游标
return self.cur
def __exit__(self, exc_type, exc_val, exc_tb):
# 提交数据库并执行
self.conn.commit()
# 关闭游标
self.cur.close()
# 关闭数据库连接
self.conn.close()
if __name__ == '__main__':
with DB() as db:
db.execute('select user_id from wz_group_chat_user where group_id = 104')
res = db.fetchall()
print(res)
注意:
python代码向数据库中插入字符串的时候如下所示需要处理一下(也可防止sql注入):
pymysql.escape_string(data_updata)
pymysql.escape_string(字符串变量)
实例
sql注入:
https://www.cnblogs.com/flhw/p/13705930.html
https://www.jianshu.com/p/078df7a35671
3.异常处理
with语句的表达式的作用是返回一个遵循特定协议的对象,该对象必须定义一个 __enter__方法和一个__exit__方法。
除了self参数,__enter__方法不接受任何参数,如果有as变量(as子句是可选项 ),返回值赋给as后的变量。
除了self参数,__exit__方法还带有三个位置参数:
一个异常类型( exc_type )
一个异常实例( exc_instance )
一个回溯( traceback )
无异常时它们全为None,但如果在代码块内有异常发生,则参数被填充
上下文管理器必须定义__exit__方法,该方法可以选择性地处理包装代码块中出现的异常,或者处理其他需要关闭上下文管理器状态的事情。如果__exit__方法接收一个异常,它可以
返回False实现异常的传播
返回True终止异常
抛出一个不同的异常,它将替代异常被发送出去
如下面这个类只处理特定异常的类
# 处理特定的异常类
class HandleValueError(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_instance, traceback):
if not exc_type: # 如果类型异常返回True
return True
if issubclass(exc_type,ValueError): # 如果异常类型为ValueError的子类则处理异常
print('Handling ValueError: %s.' % exc_instance)
return True
return False # 传播任何其他异常
# 不包括子类,例如想要捕获一个给定的异常类,但不希望显式地捕获它的子类,
# 在传统的except代码块中不能这样做(也不该这样做)
class ValueErrorSubclass(ValueError):
pass
# 使用这个上下文管理器只会处理ValueError,而不会处理它的子类ValueErrorSubclass
class HandleValueError(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_instance, traceback):
if exc_type == ValueError:
print('Handling ValueError: %s.' % exc_instance)
return True
return False