上下文管理器
1.与装饰器的区别
上下文管理器是装饰器的近亲,装饰器用于包装函数,上下文管理器用于包装任意代码块.
上下文管理器最常用的场合--作为确保资源被正确清理的一种方式.
2.上下文管理器举例
打开文件-打开文件必须确保其能关闭,这就构成了一种上下文的关系
try:
f = open("test.txt")
contents = f.read()
finally:
f.close() # 无论如何文件必须确保关闭
3.with
语句
我们知道with
语句打开文件会自动帮我们关闭文件,这个with
语句就起到了上下文管理器的作用.下面会介绍原理.
with open("test.txt", "r") as f:
contents = f.read()
3.__enter__
和__exit__
方法
with
语句会调用对象的__enter__
和__exit__
方法,在上面的例子中open
就是一个对象(Python中一切皆对象).__enter__
方法的返回值会赋值给后面的变量.__enter__
就是上文管理__exit__
就是下文管理.
写一个自己的上下文管理器.
class MyOpen(object):
def __init__(self, path, method):
self.file = open(path, method)
def __enter__(self):
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
with MyOpen("ghostdriver.log", "r") as f:
contents = f.readlines()
后面会解释__exit__
方法中的后三个参数
4.with
调用__enter__
和__exit__
的过程
In [21]: class ContextManager(object):
...: def __init__(self):
...: self.entered = False
...: def __enter__(self):
...: self.entered = True
...: def __exit__(self, exc_type, exc_val, exc_tb):
...: self.entered = False
...:
...:
In [22]: cm = ContextManager()
In [23]: print(cm.entered)
False
In [24]: with cm:
...: print(cm.entered)
...:
True
In [25]: print(cm.entered)
False
使用
with
语句会先调用with
后面类的__enter__
方法,with语句块结束后会调用__exit__
方法
In [26]: class ContextManager(object):
...: def __init__(self):
...: self.entered = False
...: def __enter__(self):
...: self.entered = True
...: print("调用__enter__")
...: def __exit__(self, exc_type, exc_val, exc_tb):
...: self.entered = False
...: print("调用__exit__")
...:
...:
...:
In [27]: cm = ContextManager()
In [28]: with cm:
...: ...
...:
调用__enter__
调用__exit__
5.处理异常
__exit__
方法可以捕获包装代码块中的异常.
对__exit__
函数中的参数解释一下:
exc_type
:异常类型exc_val
:异常实例exc_tb
:回溯
如果没有异常则以上三个参数均为None
__exit__
对异常的处理:
- 返回
True
终止异常 - 返回
False
传播异常
什么都不return:
class ContextManager(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type)
print(exc_val)
print(exc_tb)
with ContextManager():
1 / 0
output:
<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x7f3a97015cc8>
Traceback (most recent call last):
File "/home/kain/PycharmProjects/Python_code/2019暑假/05-上下文管理器-异常.py", line 15, in <module>
1 / 0
ZeroDivisionError: division by zero
return True:
class ContextManager(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type)
print(exc_val)
print(exc_tb)
return True
with ContextManager():
1 / 0
output:
<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x7f3a222e3d48>
return False:
class ContextManager(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type)
print(exc_val)
print(exc_tb)
return False
with ContextManager():
1 / 0
output:
<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x7f268176fc48>
Traceback (most recent call last):
File "/home/kain/PycharmProjects/Python_code/2019暑假/05-上下文管理器-异常.py", line 16, in <module>
1 / 0
ZeroDivisionError: division by zero
6.使用场景
关闭资源
比如连接数据库时必须要关闭数据库,像这种打开后必须关闭的操作可以用上下文管理器的操作.
Python连接MySQL数据库的上下文管理器例子:
import pymysql
class Mysql(object):
def __init__(self, host, username, passwd, database):
self.host = host
self.username = username
self.passwd = passwd
self.database = database
self.cursor = None
def __enter__(self):
self.db = pymysql.connect(self.host, self.username, self.passwd, self.database)
self.cursor = self.db.cursor()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_val:
# 如果出现异常
print("An Exception: %s." % exc_val)
self.db.close()
return True
def executeSql(self, sql):
self.cursor.execute(sql)
results = self.cursor.fetchall()
return results
with Mysql("127.0.0.1", "root", "kaindb", "test") as m:
results = m.executeSql("SELECT * FROM users;")
for each in results:
print(each)
output:
('admin', '123456')
('kainhuck', '123456')
处理异常
见上条内容
7.contextlib.contextmanager
装饰器
可以利用这个装饰器将一个函数变成上下文管理器
import contextlib
@contextlib.contextmanager
def contextManager():
try:
yield
except Exception as e:
print(e)
if __name__ == '__main__':
with contextManager():
1 / 0
output:
division by zero
注意:被装饰的函数需要返回单个值(yield)