前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python高阶教程-上下文管理器

python高阶教程-上下文管理器

作者头像
羽翰尘
修改2019-11-26 16:07:03
3810
修改2019-11-26 16:07:03
举报
文章被收录于专栏:技术向技术向

本文由腾讯云+社区自动同步,原文地址 http://blogtest.stackoverflow.club/book/senior_python/context/

本篇内容来自原创小册子《python高阶教程》,点击查看目录

从资源的释放说起

我们知道在打开一个文件后必须关闭、打开一个socket之后也必须关闭,但是总会由于代码的比较复杂或其他原因忘记释放这些资源,导致未定义的后果。

关闭这些资源其实就是为了给后续代码一个“未曾破坏”的运行环境,即在使用这些资源的前后,应保证上下文环境是相同的。与嵌入式编程中的中断需要保存现场、恢复现场有些相似。

在python中是用with语句来实现上下文管理的。

with语句的执行流程

在python中使用with进行上下文的管理,with语句的执行过程如下:

  1. 计算表达式的值,返回一个上下文管理器对象
  2. 加载上下文管理器对象的exit()方法,但不执行
  3. 调用上下文管理器对象的enter()方法
  4. 如果with语句设置了目标对象,则将enter()方法的返回值赋给目标对象
  5. 执行with中的代码块
  6. 如果5中的代码正常结束,调用上下文管理器对象的exit()方法,其返回值直接忽略。
  7. 如果5中的代码发生异常,调用上下文管理器对象的exit()方法,并将异常类型、异常值和traceback传递给exit()方法。如果exit()方法返回值为false,则异常会被重新抛出;如果其返回值为true,则视为异常已经被处理,程序继续执行。

使用类实现上下文

在类中是通过__enter__()__exit__()方法实现的。下面是一个简单的例子。

代码语言:txt
复制
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)

运行上述代码后,输出为

代码语言:txt
复制
entering
ins' num is 1
exiting

可以看出,只要在类中实现了__enter____exit__这两个方法,就可以实现一个简单的上下文管理。

在类实现的上下文管理器中进行异常处理

代码语言:txt
复制
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)

上述代码执行后输出如下:

代码语言:txt
复制
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。下面是一个简单的应用例子。

代码语言:txt
复制
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)

执行上述代码后,运行结果如下:

代码语言:txt
复制
entering
test num is 1
exiting

可以看出,yield关键词用于产生一个生成器,这个生成器又被上下文管理器封装,最后由为with语句返回,即test_num.

在生成器实现的上下文管理器中进行异常处理

使用类的方法进行上下文管理时,异常是作为参数传递的,那使用生成器进行上下文管理时应该怎样做呢?首先想到用try...except语句,如下面的代码所示:

代码语言:txt
复制
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"))

上述代码运行后,输出为:

代码语言:txt
复制
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关键词后执行的。了解这个执行顺序后,就可以对上下文管理中出现的错误进行处理。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-09-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从资源的释放说起
  • with语句的执行流程
  • 使用类实现上下文
  • 在类实现的上下文管理器中进行异常处理
  • 使用生成器实现上下文管理器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档