with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理
操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
1.术语
要使用 with 语句,首先要明白上下文管理器这一概念。有了上下文管理器,with 语句才能工作。
上下文管理协议(Context Management Protocol): 包含方法 __enter__()
和 __exit__()
,支持该协议的对象要实现这两个方法。
上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__()
和 __exit__()
方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with
语句块上下文中的进入与退出操作。通常使用 with
语句调用上下文管理器,也可以通过直接调用其方法来使用。
运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__()
和 __exit__()
方法实现,__enter__()
方法在语句体执行之前进入运行时上下文,__exit__()
在语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。
上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象。
语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 __enter__()
方法,执行完语句体之后会执行 __exit__()
方法。
2.with语法格式
with context_expression [as target(s)]:
pass
3.自定义上下文管理器
要实现自定义上下文管理器
必须要实现__enter__()
和__exit__()
两个方法。
class MyResource:
def __enter__(self):
print('enter')
def __exit__(self,exc_type, exc_value, exc_tb):
print('exit')
我们完成了一个名为MyResource
的上下文管理器,后面我们详细介绍上下文管理器。__exit__()
方法必须接收4
个参数
4.with执行顺序
在with
语句运行上下文管理器,先执行__enter__()
方法然后执行with-body
里面的代码,最后执行__exit__()
方法。我们接下来验证一下
class MyResource:
def __enter__(self):
print('enter')
def __exit__(self,exc_type, exc_value, exc_tb):
print('exit')
with MyResource():
print("with-body")
#输出
"enter"
"with-body"
"exit"
5.with的as语句
如果指定了as
子句的话,会将上下文管理器的 __enter__()
方法的返回值赋值给 target(s)。target(s) 可以是单个变量
或多个变量
class MyResource:
def __enter__(self):
print('enter')
a = 1
return a
def __exit__(self,exc_type, exc_value, exc_tb):
print('exit')
with MyResource() as resource:
print(resource)
#输出
"enter"
1
"exit"
__enter__
中可以返回多个变量
class MyResource:
def __enter__(self):
print('enter')
return ('张三',12,'男')
def __exit__(self,exc_type, exc_value, exc_tb):
print('exit')
with MyResource() as resource:
print(resource)
# 输出
enter
('张三', 12, '男')
exit
6.exit
在上面例子中,大家看到__exit__
方法参数必须是4个,这是为什么呢?当程序在with-body
中抛出异常,可以在__exit__
中处理异常。
class MyResource:
def __enter__(self):
print('enter')
return self
def __exit__(self,exc_type, exc_value, exc_tb):
print("exc_type:",exc_type)
print("exc_value:",exc_value)
print("exc_tb:", exc_tb)
with MyResource() as resource:
1/0 #会抛出异常
print('with-body')
#输出
enter
exc_type: <class 'ZeroDivisionError'>
exc_value: division by zero
exc_tb: <traceback object at 0x02AD21E8>
Traceback (most recent call last):
File "re/re1.py", line 13, in <module>
1/0
ZeroDivisionError: division by zero
从上面代码的数据结果来看,不管with-body
语句是否抛出异常,最后都会执行__exit__
方法,如果with-body
语句没有异常,那么exc_tpey
,exc_value
,exc_tb
的值都是none
,with-body
有异常,那么exc_tpye
(异常类型),exc_value
(异常的值),exc_tb
(异常信息)
exit 返回值
__exit__
方法中也是有返回值的,但是返回值只有两种True
和False
。在上面中我们没有__exit__
方法中写return也没有报错,这是因为如果不写,方法会默认返回None
,python中None
对应boolean的值就是False
。
接下来我们继续探讨下,返回True
和False
有什么区别?
exit 返回Fasle
class MyResource:
def __enter__(self):
print('enter')
return self
def __exit__(self,exc_type, exc_value, exc_tb):
print('exit')
return False
try:
with MyResource() as resource:
1/0 #会抛出异常
print('with-body')
except Exception as ex:
print(ex)
# 输出
enter
exit
division by zero
exit 返回True
class MyResource:
def __enter__(self):
print('enter')
return self
def __exit__(self,exc_type, exc_value, exc_tb):
print('exit')
return True
try:
with MyResource() as resource:
1/0 #会抛出异常
print('with-body')
except Exception as ex:
print(ex)
# 输出
enter
exit
我们写了两组代码,一组在__exit__
返回False,一组在__exit__
返回True。通过两组输出,我们对比发现,当在__exit__
方法中返回True时,try except
会捕获不到异常,当返回值为False时,可以在try except
捕获到异常。python为我们处理异常提供了非常灵活的方式,我们想在内部处理异常在__exit__
方法中返回True,如果想在外面处理异常,在__exit__
方法中返回False
7.with使用场景
相信大家到这里,也已经基本掌握with的用法。with 语句适用于对资源进行访问的场合,如访问文件,连接数据库,线程等都比较适合使用with。下面为大家写个用with来处理数据库连接的代码
import psycopg2
class MyPgsql:
def __enter__(self):
#建立数据库连接
self.conn = psycopg2.connect(database='GIS_GEO_DATA',user='postgres',password='1qa2ws3ed',host='103.248.102.20',port='5432')
self.cur = conn.cursor()
return self
def __exit__(self,exc_type,exc_value,exc_tb):
#关闭数据库连接
self.cur.close()
self.conn.close()
return True
def qurey(self):
#操作查询
print('查询数据库')
self.cur.execute(tmp)
self.conn.commit()
with MyPgsql() as pg:
pg.qurey()
8.contextlib简化上下文管理器
上面讲定义一个上下文管理器需要__enter__
和__exit__
方法。其实python中给我们提供一个contextlib
库,将一个函数包装成上下文管理器。
import contextlib
@contextlib.contextmanager
def file_open(file_name):
print("file open")
yield {}
print("file end")
with file_open("test.txt") as f:
print("file processing")