zoukankan      html  css  js  c++  java
  • python上下文管理器

    上下文管理器和with模块

    上下文管理器对象存在的目的是管理with语句。with语句的目的是简化try/finally模式。这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码异常,return语句调用或sys.exit()调用而中止,也会执行操作。finally子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。

    上下文管理器包含__enter__和__exit__两个方法。with语句开始运行时,会在上下文管理器对象上调用__enter__方法。with语句运行结束后,会在上下文管理器对象上调用__exit__方法,以此扮演finally子句的角色

    创建一个mirror.py文件,演示把文件对象当成上下文管理器使用:

    with open('mirror.py') as fp:  #fp绑定到打开的文件上,因为文件的方法返回self
        src = fp.read(60)              #读取数据
    
    len(src)
    print(fp) 
    print(fp.closed)
    print(fp.encoding)
    print(fp.read(60))
    
    
    #结果
    <_io.TextIOWrapper name='mirror.py' mode='r' encoding='cp936'>   #fp对象仍然可用
    True             
    cp936          #读取fp对象的属性
    ValueError: I/O operation on closed file.   #with块的末尾,调用TextIOWrapper.__exit__方法把文件关闭了

    第一行代码中,执行with后面的表达式得到的结果是上下文管理器对象,不过,把值绑定到目标变量上(as子句)是在上下文管理器对象上调用__enter__方法的结果。

    示例中open()函数返回TextIOWrapper类实例,而该实例的__enter__方法返回self,__enter__方法除了会返回上下文管理器对象之外,还可能返回其他对象。

    不管控制流程以哪种方式退出with块,都会在上下文管理器对象上调用__exit__方法,而不是在__enter__方法返回的对象上调用。

    with语句的as子句是可选的。对于open()函数来说,必须加上as子句以便获取文件引用。而有些上下文管理器会返回None。

    一个上下文管理器类:

    class LookingGlass:
        def __enter__(self):  #除了self不传入其他参数
            import sys
            self.original_write = sys.stdout.write   #把原来的sys.stdout.write保存在一个实例属性中
            sys.stdout.write = self.reverse_write    #替换成自己编写的方法
            return 'JABBERWOCKY'                     #返回字符串
    
        def reverse_write(self, text):          #内容反转打印
            self.original_write(text[::-1])
    
        def __exit__(self, exc_type, exc_val, exc_tb):  
            import sys
            sys.stdout.write = self.original_write   #将方法改回
            if exc_type is ZeroDivisionError:     #异常检测
                print('please DO NOT divide by zero!')
                return True

    解释器调用__enter__方法时,除了隐式的self之外,不会传入任何参数。传递给__exit__的有三个参数:

    exc_type              //异常类

    exc_value            //异常实例。有时会有参数传递给异常构造方法,这些参数可以使用exc_value.args获取

    traceback             //traceback对象

    测试:

    from mirror import LookingGlass
    with LookingGlass() as what:   #上下文管理器上调用__enter__方法,把返回结果绑定到what上
        print('hello world!')
        print(what)
    
    print(what)
    print('hello world!')

    结果:

    !dlrow olleh
    YKCOWREBBAJ    #打印出的内容是反向的
    JABBERWOCKY    #with语句执行完毕,输出不再反向
    hello world!    

    也可用在with块之外使用LookingGlass类:

    from mirror import LookingGlass
    manager = LookingGlass()      #实例化
    print(manager)      #审查对象
    moster = manager.__enter__()   #调用__enter__
    print(moster == 'JABBERWOCKY')   #见结果,输出反向
    print(moster)           #输出反向
    print(manager)        #输出反向
    
    manager.__exit__(None, None, None)  #调用__exit__还原函数
    print(moster == 'JABBERWOCKY')
    print(moster)
    
    #结果
    <mirror.LookingGlass object at 0x0000016F4A5647B8>
    eurT
    YKCOWREBBAJ
    >8B7465A4F6100000x0 ta tcejbo ssalGgnikooL.rorrim<
    True     #输出结果正常
    JABBERWOCKY    

    contextlib模块中的实用工具

    closing                          //如果对象提供了close方法,但没有实现__enter__/__exit__协议,那么可用使用这个函数构建上下文管理器

    suppress                       //构建临时忽略指定异常的上下文管理器

    @contextmanager          //这个装饰器把简单的生成器函数变为上下文管理器

    ContextDecorator         //这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。

    ExitStack                      //这个上下文管理器能进入多个上下文管理器。with块结束时,ExitStack按照后进先出的顺序调用栈中各个上下文管理器的__exit__方法。

    @contextmanager

    示例,类似Lookingglass:

    @contextlib.contextmanager   #应用装饰器
    def looking_glass():
        import sys
        original_write = sys.stdout.write
        
        def reverse_write(text):
            original_write(text[::-1])
            
        sys.stdout.write = reverse_write()
        yield 'JABBERWOCKY'   #产出一个值,这个值会绑定在with语句中as子句的目标变量上。执行with语句块中代码时,这个函数会在这个点暂停
        sys.stdout.write = original_write    #控制权跳出with块,继续执行yield语句之后的代码

    @contextlib.contextmanager装饰器会把函数包装成实现__enter__和__exit__方法的类。
    1.这个类__enter__方法有如下作用
    (1)调用生成器函数,保存生成器对象
    (2)调用next(),执行到yield关键字所在位置
    (3)返回next()产出的值,以便把产出的值绑定的with/as语句中的目标变量上

    2.with块终止时,__exit__方法会做以下几件事情

    (1)检查有没有把异常传递给exc_type;如果有,调用gen.throw(exception),在生成器函数定义体中包含yield关键字的那一行抛出异常

    (2)否则调用next()函数,继续执行生成器函数定义体中的yield语句之后的代码。

    上面looking_glass()函数有一个错误,2(1)步骤如果抛出错误,却没有错误处理代码,将无法执行sys.stdout.write = original_write。

    添加异常处理代码:

    @contextlib.contextmanager   #应用装饰器
    def looking_glass():
        import sys
        original_write = sys.stdout.write
    
        def reverse_write(text):
            original_write(text[::-1])
    
        sys.stdout.write = reverse_write()
        msg = ''
        try:
            yield 'JABBERWOCKY'
        except ZeroDivisionError:
            msg = 'Please DO NOT divide by zero!'
        finally:
            sys.stdout.write = original_write
            if msg:
                print(msg)

    使用@contextmanager装饰器时,要把yield语句放在try/finally语句中(或者with语句中),因为永远不知道用户会在上下文管理器做什么。

    以上来自《流畅的python》

  • 相关阅读:
    Gecko Bootloader的介绍(Silicon Labs)【一】
    使用模板新建ZigBee工程的方法
    代码控制ZigBee网络密钥的生成
    Ubuntu20编译最新版Android源码教程
    C和C++常用代码片段整理
    Java易错的知识点整理
    仿IntelliJ Darcula的Swing主题FlatLaf使用方法
    PuTTYTabManager汉化版
    WinSCP整合SecureCRT打开终端
    异想家博客图片批量压缩程序
  • 原文地址:https://www.cnblogs.com/lht-record/p/10328069.html
Copyright © 2011-2022 走看看