Python上下文管理器和with块详解
上下文管理器和with块,具体内容如下
上下文管理器对象存在的目的是管理with语句,就像迭代器的存在是为了管理for语句一样。
with语句的目的是简化try/finally模式。这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常、return语句或sys.exit()调用而中止,也会执行指定的操作。finally子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。
==上下文管理器协议包含enter和exit两个方法==。with语句开始运行时,会在上下文管理器对象上调用enter方法。with语句运行结束后,会在上下文管理器对象上调用exit方法,以此扮演finally子句的角色。
==执行with后面的表达式得到的结果是上下文管理器对象,把值绑定到目标变量上(as子句)是在上下文管理器对象上调用enter方法的结果==。with语句的as子句是可选的。对open函数来说,必须加上as子句,以便获取文件的引用。不过,有些上下文管理器会返回None,因为没什么有用的对象能提供给用户。
withopen('mirror.py')asfp: ...
自定义的上下文类:
classA: def__init__(self,name): self.name=name def__enter__(self): print('enter') returnself.name def__exit__(self,exc_type,exc_val,exc_tb): print('gone') withA('xiaozhe')asdt: print(dt)
contextlib模块
contextlib模块中还有一些类和其他函数,使用范围更广。
closing:如果对象提供了close()方法,但没有实现enter/exit协议,那么可以使用这个函数构建上下文管理器。
suppress:构建临时忽略指定异常的上下文管理器。
@contextmanager:==这个装饰器把简单的生成器函数变成上下文管理器==,这样就不用创建类去实现管理器协议了。
ContextDecorator:这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数
ExitStack:这个上下文管理器能进入多个上下文管理器。with块结束时,ExitStack按照后进先出的顺序调用栈中各个上下文管理器的exit方法。
==使用最广泛的是@contextmanager装饰器,因此要格外留心。这个装饰器也有迷惑人的一面,因为它与迭代无关,却要使用yield语句==。
使用@contextmanager
@contextmanager装饰器能减少创建上下文管理器的样板代码量,不用编写一个完整的类定义enter和exit方法,而只需实现有一个yield语句的生成器,生成想让enter方法返回的值。
在使用@contextmanager装饰的生成器中,yield语句的作用是把函数的定义体分成两部分:==yield语句前面的所有代码在with块开始时(即解释器调用enter方法时)执行,yield语句后面的代码在with块结束时(即调用exit方法时)执行==。
importcontextlib @contextlib.contextmanager deftest(name): print('start') yieldname print('end') withtest('zhexiao123')asdt: print(dt) print('doingsomething')
实现原理
contextlib.contextmanager装饰器会把函数包装成实现enter和exit方法的类。类的名称是_GeneratorContextManager。
这个类的enter方法有如下作用:
1.调用生成器函数,保存生成器对象(这里把它称为gen)。
2.调用next(gen),执行到yield关键字所在的位置。
3.返回next(gen)产出的值,以便把产出的值绑定到with/as语句中的目标变量上。
with块终止时,exit方法会做以下几件事:
1.检查有没有把异常传给exc_type;如果有,调用gen.throw(exception),在生成器函数定义体中包含yield关键字的那一行抛出异常。
2.否则,调用next(gen),继续执行生成器函数定义体中yield语句之后的代码。
异常处理
为了告诉解释器异常已经处理了,exit方法会返回True,此时解释器会压制异常。如果exit方法没有显式返回一个值,那么解释器得到的是None,然后向上冒泡异常。
使用@contextmanager装饰器时,默认的行为是相反的:装饰器提供的exit方法假定发给生成器的所有异常都得到处理了,因此应该压制异常。如果不想让@contextmanager压制异常,必须在被装饰的函数中显式重新抛出异常。
上面的代码有个bug:如果在with块中抛出了异常,Python解释器会将其捕获,然后在test函数的yield表达式里再次抛出。但是,那里没有处理错误的代码,因此test函数会中止。
使用@contextmanager装饰器时,要把yield语句放在try/finally语句中,因为我们永远不知道上下文管理器的用户会在with块中做什么。
importcontextlib @contextlib.contextmanager deftest(name): print('start') try: yieldname except: raiseValueError('error') finally: print('end') withtest('zhexiao123')asdt: print(dt) print('doingsomething')
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。