上下文管理
前言:
在对文件进行操作的时候,常会用到with open()...
,如下
这样做会在 do something之后,自动关闭文件f.close()
,这里就是 上下文管理器
with open('路径') as f:
do something
作用
上下文管理器是指在一段代码执行之前执行一段代码,用于一些预处理工作;执行对应代码之后再执行一段代码,用于一些收尾工作。
详解
一个类,实现了__enter__(self)
方法和__exit__(self,exc_type,exc_val,exc_tb)
,就是一个上下文管理器
with EXPR as VAR:
BLOCK
- 执行
EXPR
获取上下文管理器 - 执行上下文管理器中的
__enter__
方法,返回值赋值给VAR
,这里的as VAR
可以省略 - 执行代码块BLOCK,这里的VAR可以当做普通变量使用
- 最后调用上下文管理器中的的
__exit__
方法。 __exit__
方法有三个参数:exc_type, exc_val, exc_tb。如果代码块BLOCK发生异常并退出,那么分别对应异常的type、value 和 traceback。否则三个参数全为None。__exit__
方法的返回值可以为True或者False。如果为True,那么表示异常被忽视,相当于进行了try-except操作;如果为False,则该异常会被重新raise。
demo1
一个最简单的例子
class MyManager():
def __enter__(self):
print('从enter进来,做一些预处理的工作')
def __exit__(self,exc_type,exc_val,exc_tb):
print('执行完目标代码之后,做一些收尾的工作')
with MyManager():
print('我就是目标代码块')
输出:
从enter进来,做一些预处理的工作
我就是目标代码块
执行完目标代码之后,做一些收尾的工作
with
后面
demo2
前面使用with open
的时候还用了as
,这里的as
就是__enter__
的返回值,一般是self 本身
class MyManager():
def __enter__(self):
print('从enter进来,做一些预处理的工作')
return 11
def __exit__(self,exc_type,exc_val,exc_tb):
print('执行完目标代码之后,做一些收尾的工作')
with MyManager() as m:
print(m)
print('我就是目标代码块')
输出如下:
从enter进来,做一些预处理的工作
11 #这个就是__enter__的返回值,一般是返回self,这里为了解析,写死为11
我就是目标代码块
执行完目标代码之后,做一些收尾的工作
得出结论: as后的变量,就是
__enter__
方法的返回值,一般返回的是self
demo3
__exit__
方法的几个参数分别是什么?
class MyManager():
def __init__(self, name):
self.name = name
def __enter__(self):
print('从enter进来,做一些预处理的工作')
return self
def __exit__(self,exc_type,exc_val,exc_tb):
print('__exit__的参数 exc_type为:{}, exc_val为:{},exc_tb为:{}'.format(exc_type,exc_val,exc_tb))
print('执行完目标代码之后,做一些收尾的工作')
with MyManager(name='测试') as m:
print(m.name)
print('我就是目标代码块')
输出:
从enter进来,做一些预处理的工作
测试
我就是目标代码块
__exit__的参数 exc_type为:None, exc_val为:None,exc_tb为:None
执行完目标代码之后,做一些收尾的工作
是三个
None
,因为这里的这些参数,顾名思义都是一些抛出错误的信息
尝试抛出错误
class MyManager():
def __init__(self, name):
self.name = name
def __enter__(self):
print('从enter进来,做一些预处理的工作')
return self
def __exit__(self,exc_type,exc_val,exc_tb):
print('__exit__的参数 exc_type为:{}, exc_val为:{},exc_tb为:{}'.format(exc_type,exc_val,exc_tb))
print('执行完目标代码之后,做一些收尾的工作')
return True
with MyManager(name='测试') as m:
# 输出一个不存在的变量
print(x)
输出:
从enter进来,做一些预处理的工作
__exit__的参数 exc_type为:<class 'NameError'>, exc_val为:name 'x' is not defined,exc_tb为:<traceback object at 0x0000022AD1194400>
执行完目标代码之后,做一些收尾的工作
# 抛出错误
Traceback (most recent call last):
NameError: name 'x' is not defined
三个参数分别是: 异常的类型,异常的详情,和异常的对象
如果__exit__
方法有返回值,且为True,则程序不会报错,相当于进行了 try ... except
上下文管理器根据__exit__
方法的返回值来决定是否抛出异常,如果没有返回值或者返回值为 False
,则异常由上下文管理器处理,如果为 True
则由用户自己处理
class MyManager():
def __init__(self, name):
self.name = name
def __enter__(self):
print('从enter进来,做一些预处理的工作')
return self
def __exit__(self,exc_type,exc_val,exc_tb):
print('__exit__的参数 exc_type为:{}, exc_val为:{},exc_tb为:{}'.format(exc_type,exc_val,exc_tb))
print('执行完目标代码之后,做一些收尾的工作')
# 加了这一行代码,如果返回的是False 程序还是会报错
return True
with MyManager(name='测试') as m:
# 输出一个不存在的变量
print(x)
输出
从enter进来,做一些预处理的工作
__exit__的参数 exc_type为:<class 'NameError'>, exc_val为:name 'x' is not defined,exc_tb为:<traceback object at 0x00000238B8224440>
执行完目标代码之后,做一些收尾的工作
__exit__
方法里可以对要执行的代码块的异常信息进行捕获,处理,但是如果代码块有多个异常,只能捕获到第一个异常
return Fasle
的情况
class A:
def __enter__(self):
print('执行了enter')
def __exit__(self,exec_type,exc_val,exc_tb):
print('exit:',exec_type,exc_val,exc_tb)
return False
with A():
print(i)
输出:
执行了enter
exit: <class 'NameError'> name 'i' is not defined <traceback object at 0x0000016415B7C6C0>
Traceback (most recent call last):
File "d:\study\demo\上下文管理器.py", line 41, in <module>
print(i)
NameError: name 'i' is not defined
内置库contextlib的使用
自定义类实现
__enter__
和__exit__
方法可以创建上下文管理器,python内置的contextlib库,使得上线文管理器更加容易使用
contextlib.contextmanager
装饰器
from contextlib import contextmanager
@contextmanager
def fun():
'''这里的内容相当于__enter__'''
yeild # 有返回值的话,相当于__enter__的返回值 也就是 as 后面的变量的值
'''以yeild为分界线,这里的内容相当于 __exit__'''
上述demo相当于:
from contextlib import contextmanager
@contextmanager
def fun():
print('从enter进来,做一些预处理的工作')
yield {'name':'测试'}
print('执行完目标代码之后,做一些收尾的工作')
with fun() as d:
print(d.get('name',None))
print('我就是目标block代码块')
输出:
从enter进来,做一些预处理的工作
测试
我就是目标block代码块
执行完目标代码之后,做一些收尾的工作
尝试捕获异常
@contextmanager
def test():
print('111')
yield {'name':'kobe'}
print('22')
with test():
print(i)
输出:
111
Traceback (most recent call last):
File "d:\study\demo\上下文管理器.py", line 40, in <module>
print(i)
NameError: name 'i' is not defined
遇到异常直接抛出,不会执行yield后的代码
需要注意的是,@contextlib.contextmanager
不像之前介绍的__exit__()
方法,遇到异常也会执行。也就是with语句块抛出异常的话,yield
后面的代码将不会被执行。所以,必要时你需要对yield
语句使用”try-except-finally”。
@contextmanager
def test():
try:
print('111')
yield {'name':'kobe'}
print('22')
except Exception as e:
pass
finally:
print('333')
with test() as x:
print(x)
print(i)#故意输出个NameError
输出:
111
{'name': 'kobe'}
# 不会输出yield后的 22
333
拓展:
查看drf认证源码时,对请求执行
self.initial(request,*args,**kwargs)
时,依次执行的三个方法,self.perform_authentication(request)、self.check_permissions(request)、self.check_throttles(request) 认证、权限、限流, 认证第一步执行request.user
查看源码如下:
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
# 查看这里上下文的处理
with wrap_attributeerrors():
self._authenticate()
return self._user
查看 wrap_attributeerors
上下文管理器的代码如下:
@contextmanager
def wrap_attributeerrors():
"""
Used to re-raise AttributeErrors caught during authentication, preventing
these errors from otherwise being handled by the attribute access protocol.
"""
try:
yield
except AttributeError:
info = sys.exc_info()
exc = WrappedAttributeError(str(info[1]))
raise exc.with_traceback(info[2])
这里的info
对象是一个元祖,如下:
(<class 'AttributeError'>, AttributeError("'int' object has no attribute 's'"), <traceback object at 0x0000021FAF9B9900>)
和上述自定义上下文管理器中的__exit__(self,exec_type,exc_val,exc_tb)
中的三个参数是一样的,第一个为异常的类型,第二个为异常的描述,第三个为异常的对象
遍历一下info
#info[0], type(info[0])
<class 'AttributeError'> <class 'type'>
#info[1], type(info[1])
'int' object has no attribute 's' <class 'AttributeError'>
#info[2], type(info[2])
<traceback object at 0x00000257914DB480> <class 'traceback'>
最后抛出AttributeError
异常,这里这么写的原因也在wrap_attributeerrors方法
的doc
写明了
当认证时,去捕获AttributeErrors,预防描述符协议去处理这些异常
简单理解: 在user源码中,如果request没有_user属性,去执行
self._authenticate()
方法,如果执行这个方法的过程中有属性访问的异常,就抛出