网站首页 文章专栏 上下文管理器
我们知道在打开一个文件后必须关闭、打开一个socket之后也必须关闭,但是总会由于代码的比较复杂或其他原因忘记释放这些资源,导致未定义的后果。
关闭这些资源其实就是为了给后续代码一个“未曾破坏”的运行环境,即在使用这些资源的前后,应保证上下文环境是相同的。与嵌入式编程中的中断需要保存现场、恢复现场有些相似。
在python中是用with语句来实现上下文管理的。
在python中使用with进行上下文的管理,with语句的执行过程如下:
在类中是通过__enter__()
和__exit__()
方法实现的。下面是一个简单的例子。
class my_context():
def __init__(self, num):
self.num = num
def __enter__(self):
print("entering")
return self
def __exit__(self, exception_type,
exception_value,
traceback):
print("exiting")
## test it
with my_context(1) as ins:
print("ins' num is", ins.num)
运行上述代码后,输出为
entering
ins' num is 1
exiting
可以看出,只要在类中实现了__enter__
和__exit__
这两个方法,就可以实现一个简单的上下文管理。
class my_context():
def __init__(self, num):
self.num = num
def __enter__(self):
print("entering")
return self
def __exit__(self, exception_type,
exception_value,
traceback):
print("exiting")
if exception_type is None:
ret = True
elif exception_type is ValueError:
print("Value error handled")
ret = True
else:
print("Unknown exception type,"
"throw it")
ret = False
return ret
## test it
print("Test 1, no exception")
with my_context(1) as ins:
print("ins' num is", ins.num)
print()
print("Test 2, value error")
with my_context(2) as ins:
print("ins' num is", ins.num)
raise(ValueError)
print()
print("Test 3, key error")
with my_context(3) as ins:
print("ins' num is", ins.num)
raise(KeyError)
上述代码执行后输出如下:
Test 1, no exception
entering
ins' num is 1
exiting
Test 2, value error
entering
ins' num is 2
exiting
Value error handled
Test 3, key error
entering
ins' num is 3
exiting
Unknown exception type,throw it
Traceback (most recent call last):
File "error_class.py", line 36, in <module>
raise(KeyError)
KeyError
可以看到,如果在执行with
代码块的时候发生了异常,可以在__exit__()
方法中进行处理。如果处理结束,返回True
,代码继续执行;如果无法处理,就返回False
,python会把这个异常继续抛出,直至被正常处理。
如果我们只是为一个简单的函数进行上下文管理,那么定义一个类略有些麻烦。好在我们还有标准库可以使用,这个标准库是contextlib
。下面是一个简单的应用例子。
from contextlib import contextmanager
@contextmanager
def process(num):
print("entering")
yield num
print("exiting")
## test it
with process(1) as test_num:
print("test num is", test_num)
执行上述代码后,运行结果如下:
entering
test num is 1
exiting
可以看出,yield
关键词用于产生一个生成器,这个生成器又被上下文管理器封装,最后由为with
语句返回,即test_num
.
在生成器实现的上下文管理器中进行异常处理
使用类的方法进行上下文管理时,异常是作为参数传递的,那使用生成器进行上下文管理时应该怎样做呢?首先想到用try...except
语句,如下面的代码所示:
from contextlib import contextmanager
@contextmanager
def process(num):
print("entering")
try:
yield num
except RuntimeError as err:
print("handled error:", err)
finally:
print("exiting")
## test it
print("Test with runtime error")
with process(1) as test_num:
print("test num is", test_num)
raise(RuntimeError("It's runtime error"))
print()
print("Test with value error")
with process(2) as test_num:
print("test num is", test_num)
raise(ValueError("It's value error"))
上述代码运行后,输出为:
Test with runtime error
entering
test num is 1
handled error: It's runtime error
exiting
Test with value error
entering
test num is 2
exiting
Traceback (most recent call last):
File "./generator_error_func.py", line 24, in <module>
raise(ValueError("It's value error"))
ValueError: It's value error
我们在try..except
语句中对RuntimeError
进行了处理,所以代码可以继续执行;没有对ValueError
处理,所以异常继续向上抛,直到控制台输出错误信息。
这里也可以看出,实际上with
代码块的内容是在紧接着yield
关键词后执行的。了解这个执行顺序后,就可以对上下文管理中出现的错误进行处理。