美文网首页
04.with语句块

04.with语句块

作者: 杨强AT南京 | 来源:发表于2018-10-27 01:09 被阅读22次

    本文主题,讲解with的使用与神奇。

    1. with语法与使用前提;
    2. with与异常处理;
    3. 上下文管理器模块contextlib;
    4. 使用with处理图形上下文;

    一、with语法与使用前提

    1. with语法

    语法一

        with  对象:
            with语句
    

    语法二

        with  对象  as  对象别名:
            with语句
    

    2. with使用前提

      先从一段运行错误的代码开始:

    #coding=utf-8
    a=20
    with a:
        print("with body")
    

    该段代码执行错误如下:

    with使用的错误情况

    该错误提示:对象a的AttributeError:__enter__。

    实际上,该错误发生的原因是:

    with中使用的对象必须实现__enter__与__exit__函数。

    备注:下面的解释引用网上的文章(https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/)。
    因为with的工作机制与流程如下:

    context_manager = context_expression
    exit = type(context_manager).__exit__  
    value = type(context_manager).__enter__(context_manager)
    exc = True   # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理
    try:
        try:
            target = value  # 如果使用了 as 子句
            with-body     # 执行 with-body
        except:
            # 执行过程中有异常发生
            exc = False
            # 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常
            # 由外层代码对异常进行处理
            if not exit(context_manager, *sys.exc_info()):
                raise
    finally:
        # 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出
        # 或者忽略异常退出
        if exc:
            exit(context_manager, None, None, None) 
        # 缺省返回 None,None 在布尔上下文中看做是 False
    

      说明:

    1. 执行 context_expression,生成上下文管理器 context_manager
    2. 调用上下文管理器的 enter() 方法;如果使用了 as 子句,则将 enter() 方法的返回值赋值给 as 子句中的 target(s)
    3. 执行语句体 with-body
    4. 不管是否执行过程中是否发生了异常,执行上下文管理器的 exit() 方法,exit() 方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 exit(None, None, None) ;如果执行过程中出现异常,则使用 sys.exc_info 得到的异常信息为参数调用 exit(exc_type, exc_value, exc_traceback)
    5. 出现异常时,如果 exit(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理
    #coding=utf-8
    class MyWith:
        def __enter__(self):
            print("enter")
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("exit")
            print(exc_type,exc_val,exc_tb)
    
    o=MyWith()
    with o:
        print("执行with过程!")
    

    输出结果如下:

    with对象的调用输出

    说明:
      1. 没有异常情况下,系统调用__exit__的时候,传递的参数都是None。
      2. 其中context_mamager就是with 后的对象,由__enter__返回,并赋值给as后的变量。
      修改代码,让__enter__返回一个对象,可以看见as后的变量就是__enter__返回对象。

    #coding=utf-8
    class MyWith:
        def __enter__(self):
            print("enter")
            return "Hello"
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("exit")
            print(exc_type,exc_val,exc_tb)
    
    o=MyWith()
    with o as b:
        print("执行with过程!",b)        #b就是__enter__返回的值
    

    执行效果如下:

    __enter__的返回值与as后面变量关系

      当with过程发生异常,则会传递异常信息,下面是一段with发生异常情况的代码:

    #coding=utf-8
    class MyWith:
        def __enter__(self):
            print("enter")
            print(1/0)      #人为制造一个异常
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("exit")
            print(exc_type,exc_val,exc_tb)
    
    o=MyWith()
    with o as b:
        print("执行with过程!",b)        #b就是__enter__返回的值
    

      由于exit中没有处理异常,所以会转移异常给调用者,下面是运行的异常输出:

    没有在exit处理异常的情况

    二、with与异常处理

      通过__exit__可以确定是否需要调用者处理异常。返回True,不需要处理,返回False需要处理
      下面是with执行过程产生异常的情路:

    #coding=utf-8
    import traceback
    class MyWith:
        def __enter__(self):
            print("enter")
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("exit")
            print("异常类型",exc_type)
            print("异常值", exc_val)
            print("异常跟踪",exc_tb)
            traceback.print_tb(exc_tb)
            print(":",traceback.format_tb(exc_tb))
            #return False    #需要调用者处理异常
            return True #调用者不处理异常
    
        def biz(self):
            print(1 / 0)  # 人为制造一个异常
    
    o=MyWith()
    
    with o as b:
        #b.biz()
        print(1 / 0)  # 人为制造一个异常
    

    下面是输出效果,可以观察__exit__传递的异常信息。

    异常传递与异常信息

    提示:异常的相关处理可以使用traceback模块中的函数。

    三、上下文管理器模块contextlib

    使用contextlib可以达到一样的效果:

    from contextlib import contextmanager
    @contextmanager
    def TheWith():
        try:
            print('enter')
            yield '返回的as后面的值'
            print("exit")
        except Exception as e:
            print(e)
            print("这里做关闭工作")
    
    with TheWith() as w:
        print("With过程:",w)
        print(1/0)
    

    运行的效果如下:

    上下文库的使用效果

    这里yield生成器前等于enter,yield生成器后等于exit。
    如果有异常,可以在with对象内部处理,不用再with过程中处理。
    下面是一段简洁代码可以说明contextlib的执行过程:

    @contextmanager
    def TheWith():
        print('enter')
        yield '返回的as后面的值'
        print("exit")
    with TheWith() as w:
        print("With过程:",w)
    

    运行效果如下:

    contextmanager控制下的执行过程

    四、with的典型应用

    1. 文件操作异常的正常关闭

    #coding=utf-8
    
    #传统写法
    file = open("with_try.py")
    try:
        data = file.read()
        print(data)
    finally:
        file.close()
    
    #简洁写法
    with open("with_try.py") as file:
        data = file.read()
        print(data)
    

    2. 使用绘图上下文

      这种使用方式在传统的c中使用比较广泛,但是异常发生需要单独的处理机制,在python中使用with得到很好的处理。
      相关的例子代码,可以参考我关于Kivy的系列文章。


    资源

    本文使用的代码的下载地址: https://github.com/QiangAI/PythonSkill/tree/master/AdvPython/03with

    相关文章

      网友评论

          本文标题:04.with语句块

          本文链接:https://www.haomeiwen.com/subject/ulqrtqtx.html