网站首页 文章专栏 python高阶教程-上下文管理器
本篇内容来自原创小册子《python高阶教程》,点击查看目录。
我们知道在打开一个文件后必须关闭、打开一个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
关键词后执行的。了解这个执行顺序后,就可以对上下文管理中出现的错误进行处理。