上下文管理器中的异常处理
这篇文章我们来看一下上下文管理器中的异常处理和标准库对于上下文管理器的支持。
回顾一下上下文管理器的特点:上下文管理器是个对象,它有和两个方法。
classContext:
def__enter__(self):
print('In enter')
returnself
def__exit__(self,*_):
print('In exit')
returnTrue
这里的方法的参数列表被我们利用收集到了一起,我们把它打印出来看看是什么内容:
classContext:
def__enter__(self):
returnself
def__exit__(self,*_):
frompprintimportpprint
pprint(_)
returnTrue
withContext():
print('In context')
# In context
# (None, None, None)
所以说,在离开上下文时,解释器会给额外传递3个位置参数。这些参数都是用于处理上下文中的异常的,所以正常状态下,他们都是。让我们尝试在上下文中抛出一个异常:
withContext():
raiseException('Raised')
# (,
# Exception('Raised',),
# )
我们依照一个普通的处理异常的语句来看一下这三个参数都是什么:
try:
raiseException('Raised')
exceptExceptionase:
print(type(e))
print(repr(e))
print(e.__traceback__)
# (,
# Exception('Raised',),
# )
可以看到,的三个参数分别表示:
异常类型;
异常对象(关于将在字符串系列中详细说明);
栈对象;
那么,为什么在上下文中抛出了异常,程序却没有异常中止呢?答案在于的返回值。如果它返回了,那么上下文中的异常将被忽略;如果是,那么上下文中的异常将被重新向外层抛出。假如在外层没有异常处理的代码,那么程序将会崩溃:
classContext:
def__enter__(self):
returnself
def__exit__(self,*_):
# 返回一个False
returnFalse
withContext():
raiseException('Raised')
# Traceback (most recent call last):
# File "C:\...py", line 33, in
# raise Exception('Raised')
# Exception: Raised
那么,如何在中处理异常呢?既然能够获取到异常对象,那么可以通过来判断异常类型,或是直接利用参数中的异常类型来判断,进而做出相应处理:
exs= [
ValueError,
IndexError,
ZeroDivisionError,
]
classContext:
def__enter__(self):
returnself
def__exit__(
self,
ex_type,
ex_value,
tb
):
ifex_typeinexs:
print('handled')
returnTrue
else:
returnFalse
withContext():
10/
# handled
try:
withContext():
raiseTypeError()
exceptTypeError:
print('handled outside')
# handled outside
那么,如果在里可能出现异常,我们该怎么办呢?很不幸,我们只能在里去手动它们。
标准库的支持
Python标准库中给出了上下文管理器的另一种实现:。它是一个装饰器。我们来简单看一下它是怎么使用的:
fromcontextlibimportcontextmanager
@contextmanager
defcontext():
print('In enter')
yield
print('In exit')
withcontext():
print('In context')
# In enter
# In context
# In exit
来和我们最初的写法比较一下:
classContext:
def__enter__(self):
print('In enter')
returnself
def__exit__(self,*_):
print('In exit')
returnTrue
withContext():
print('In context')
# In enter
# In context
# In exit
结果一样,但写法简单了许多。关于关键字,后面我们会详细介绍。这里我们只需要知道,在之前的语句扮演了的角色,而在之后的语句则扮演了的角色。那么,我们如何像一样返回一个对象呢?例如,我们打开一个文件:
@contextmanager
deffileopen(name,mod):
f=open(name,mod)
# 直接yield出去即可
yieldf
f.close()
withfileopen('a.txt','r')asf:
forlineinf:
print(line)
# 欢迎关注
#
# 微信公众号:
#
# 它不只是Python
如何处理这里面的异常呢?在处采用语句:
@contextmanager
deffileopen(name,mod):
try:
f=open(name,mod)
yieldf
except:
print('handled')
finally:
f.close()
withfileopen('a.txt','r')asf:
raiseException()
# handled
实际上,对于这类需要在离开上下文后调用方法释放资源的对象,给出了更加直接的方式:
fromcontextlibimportclosing
classA:
defclose(self):
print('Closing')
withclosing(A())asa:
print(a)
#
# Closing
这样,类的对象自动变成了上下文管理器对象,并且在离开这个上下文的时候,解释器会自动调用对象的方法(即使中间抛出了异常)。所以,针对一些具有方法的非上下文管理器对象,直接利用要便捷许多。还提供了另外一种不使用的语法糖来实现上下文功能。采用这种方式定义的上下文只是增加了一个继承关系:
fromcontextlibimportContextDecorator
classContext(ContextDecorator):
def__enter__(self):
print('In enter')
returnself
def__exit__(self,*_):
print('In exit')
returnTrue
怎么使用呢?请看:
@Context()
defcontext_func():
print('In context')
context_func()
# In enter
# In context
# In exit
上下文代码不再使用代码段,而是定义成函数,通过装饰器的方式增加了一个进入和离开的流程。我们可以根据实际情况,灵活地采取不同的写法来实现我们的功能。最后,我们再来看一个提供的功能:。它可以创建一个能够忽略特定异常的上下文管理器。有些时候,我们可能知道上下文管理器中的代码可能抛出什么异常,或者说我们不关心抛出了哪些异常,我们可以让函数直接返回,这样所有的异常就被忽略在了中。提供了一个更简便的写法,我们只需给它传入需要忽略的异常类型即可:
fromcontextlibimportsuppress
ig_exs= [
ValueError,
IndexError,
RuntimeError,
OSError,
...,
]
withsuppress(*ig_exs):
raiseValueError()
print('Nothing happens')
# Nothing happens
因为所有的非系统异常都是的子类,所以如果参数传入了,那么所有的异常都会被忽略:
fromcontextlibimportsuppress
withsuppress(Exception):
raiseOverflowError()
print('Nothing happens')
# Nothing happens
这里需要说明的是何为非系统异常。有一些异常可能来自系统问题而非程序本身,例如我们经常有经验,程序陷入死循环了,我们需要用结束它。如果你注意了后程序打印的错误信息,会发现它抛出了一个。类似这些异常(包括本身)都继承于。所以,真正的异常的父类是。关于异常的层次关系,请参阅:https://docs.python.org/3/library/exceptions.html#exception-hierarchy
友情链接: https://vonalex.github.io/
欢迎关注 它不只是Python
领取专属 10元无门槛券
私享最新 技术干货