上下文管理

前言:

在对文件进行操作的时候,常会用到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()方法,如果执行这个方法的过程中有属性访问的异常,就抛出

posted @ 2022-08-01 14:56  Alantammm  阅读(98)  评论(0编辑  收藏  举报