上下文管理器
上下文管理器与装饰器类似,它们都是包装其他代码的工具。但装饰器用于包装定义的代码块(如函数或类),而上下文管理器可以包装任意格式的代码块。
如果进入上下文管理器就一定会有退出步骤,因此上下文管理器应用最多的地方就是作为资源被正确清理的一种方式。
举个简单的例子,在python中对一个txt文件进行写入操作,写入后需要关闭文件
try:
f = open('test.txt', 'r') # 打开一个文件
f.write('hello')
finally:
f.close() # 关闭该文件
这样的写法虽然没有问题,但总是需要手动关闭文件,很容易引起漏洞,并且在经常需要对文件进行读写的代码中显得并不pythonic。
python的内置函数open能作为上下文管理器被使用,在python2.5后新增了关键字with,使用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 = '退出上下文管理器'
在python shell中执行以下操作
>>> cm = ContextManager() # 这里只创建一个ContextManager的实例
>>> cm.entered # 查看实例的entered值
'未使用上下文管理器'
# 如果使用ContextManager为上下文管理器
>>> with ContextManager() as cm:
cm.entered
'进入上下文管理器'
>>> cm.entered
'退出上下文管理器'
可以看出,with语句是进入上下文管理器的入口且默认执行__enter__
函数,而退出上下文管理器时默认执行__exit__
函数。
使用上下文管理器连接数据库
打开和关闭资源(如文件和数据库连接)是编写上下文管理器的一个重要应用。确保出现异常时正确关闭资源往往很重要,这样能够避免最终随着时间的推移而产生很多的僵尸进程。
上下文管理器的优势在于此。通过在__enter__
方法中打开资源并返回它,可以保证__exit__
方法能执行,同时也能对异常进行处理。
下面是一个连接MySql数据库的上下文管理器:
import pymysql
class DBConnection(object):
def __init__(self,dbname=None, user=None, password=None, host='localhost'):
self.dbname = dbname
self.user = user
self.password = password
self.host = host
def __enter__(self):
self.conn = pymysql.connect(
host = self.host,
user = self.user,
password = self.password,
db = self.dbname)
return self.conn.cursor()
def __exit__(self, exc_type, exc_instance, traceback):
self.conn.commit()
self.conn.close()
"""
运行程序并在shell中使用这个上下文管理器即可进行数据库操作
with DBConnection(user='root', dbname='数据库名') as conn:
sql = 'SQL语句'
conn.execute(sql)
conn.fetchall()
"""
异常处理
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
上下文管理器提供了确保资源被正确处理的优秀方式,并且能够将需要在程序中多个不同位置重复使用的异常代码封装到一个位置。
与装饰器一样,上下文管理器适用于采纳“只做一次”原则的工具,除非迫不得已需要在多处重复代码。装饰器用于封装命名函数与类,而上下文管理器更适用于封装任意代码段。