• Python with/as和contextlib上下文管理使用说明


    with/as

    使用open打开过文件的对with/as都已经非常熟悉,其实with/as是对try/finally的一种替代方案。

    当某个对象支持一种称为"环境管理协议"的协议时,就会通过环境管理器来自动执行某些善后清理工作,就像finally一样:不管中途是否发生异常,最终都会执行某些清理操作。

    用法:

    with expression [as var]:
        with_block_code
    

    当expression返回的对象是支持环境管理协议的时候,就可以使用with。as var是可选的,如果不使用as var,expression返回对象将被丢弃,如果使用as var,就会将expression的返回对象赋值给变量var。

    整个流程大致如下:先评估expression,如果支持环境管理协议,然后开始with/as语句块结构,当准备退出with语句块的时候,将执行对象中定义的善后操作。工作机制的细节见下文。

    例如,open()返回的文件对象是支持环境管理协议的,所以可以用with/as来安全地打开文件:

    with open(r'd:aca.log') as logfile:
        for line in logfile:
            print(line)
            ...more code here...
    

    整个过程是先open(),然后with/as,输出每一行后将要退出with语句块的时候,环境管理器根据文件对象中定义的操作关闭文件。

    它实际上等价于:

    myfile = open(r'd:aca.log')
    try:
        for line in myfile:
            print(line)
            ...more code here...
    finally:
        myfile.close()
    

    虽然在文件不被引用之后,垃圾回收器会自动回收这个文件对象,但是垃圾回收器的回收操作是有等待时间的。换句话说,如果不使用with/as打开文件,也不显示close()关闭文件,那么这个文件很可能会在用完之后保持空闲一段时间,然后才被垃圾回收器回收。

    with/as不仅用于文件打开/关闭,锁操作也支持环境管理协议,也就是说,在有需要的时候会自动释放锁资源。

    嵌套多个环境管理器

    在python 3.1之后,with as支持多个环境管理器,使用逗号隔开即可。

    with A() as a, B() as b:
        ...statements...
    

    它等价于嵌套的with:

    with A() as a:
        with B() as b:
            ...statements...
    

    多环境管理器管理的多个对象会在with语句块中出现异常的时候,或者执行完with语句块的时候全部自动被清理(例如文件关闭操作)。

    例如,打开两个文件,将它们的内容通过zip()合并在一起,并且同时关闭它们:

    with open('a.file') as f1, open('b.file') as f2:
        for pair in zi[(f1, f2):
            print(pair)
    

    自定义环境管理器

    无论是文件还是锁,都是别人已经写好了环境管理器的对象。我们自己也可以写环境管理器,让它可以使用with/as,这实际上属于运算符重载的范畴。

    要写自己的环境管理器,先了解with/as的工作机制的细节:

    1. 先评估expression,评估的返回结果是一个对象,这个对象要具有__enter____exit__方法,返回的对象称为"环境管理器"
    2. 然后调用环境管理器的__enter__方法。__enter__方法的返回值赋值给 as 指定的变量,或者直接丢弃(没有使用as)
    3. 然后执行with语句块中的内容
    4. 如果执行with语句块中的内容时抛出了异常,将调用__exit__(type,value,traceback)方法,其中这3个和异常相关的参数来源于sys.exc_info。如果__exit__返回值为False,则会自动重新抛异常以便传播异常,否则异常被认为合理处理
    5. 如果with语句块中的内容没有抛异常,则直接调用__exit__(None,None,None),即这三个参数都传递为None值

    看一个简单的示例:

    class TraceBlock:
        def message(self, arg):
            print('running ' + arg)
    
        def __enter__(self):
            print('starting with block')
            return self
    
        def __exit__(self, exc_type, exc_value, exc_tb):
            if exc_type is None:
                print('exited normally
    ')
            else:
                print('raise an exception! ' + str(exc_type))
                return False
    

    上面的__enter__方法返回的对象会赋值给as关键字指定的变量,在这个示例中即将对象自身返回。如果有需求,可以返回其它对象。

    上面的__exit__中,如果异常的类型为None,说明with语句块中的语句执行过程没有抛异常,正常结束即可。但是如果有异常,则要求返回False,实际上上面的return False可以去掉,因为函数没有return时默认返回None,它的布尔值代表的就时False。

    测试下:

    with TraceBlock() as action:
        action.message("test 1")
        print("reached")
        
    print('-' * 20, "
    ")
    
    with TraceBlock() as action:
        action.message("test 2")
        raise TypeError
        print("not reached")
    

    结果如下:

    starting with block
    running test 1
    reached
    exited normally
    
    --------------------
    
    starting with block
    running test 2
    raise an exception! <class 'TypeError'>
    Traceback (most recent call last):
      File "g:/pycode/list.py", line 23, in <module>
        raise TypeError
    TypeError
    

    定义环境管理器不是件简单的事。一般来说,如果不是很复杂的需求,直接使用try/finally来定义相关操作即可。

    contextlib模块

    在自定义上下文管理器的时候,可以通过contextlib模块的contextmanager装饰器来简化,这样不需要自己去写__enter____exit__

    使用contextlib.contextmanager装饰的时候,所装饰的对象必须是一个生成器函数,且该生成器函数必须只yield一个值,这个值将会被绑定到with/as的as子句的变量上。

    from contextlib import contextmanager
    
    @contextmanager
    def managed_resource(*args, **kwds):
        # Code to acquire resource, e.g.:
        resource = acquire_resource(*args, **kwds)
        try:
            yield resource
        finally:
            # Code to release resource, e.g.:
            release_resource(resource)
    
    >>> with managed_resource(timeout=3600) as resource:
    ...     # Resource is released at the end of this block,
    ...     # even if code in the block raises an exception
    

    它的执行流程是这样的:

    1. 当执行到with语句的时候,首先评估managed_resource(timeout=3600),它是一个生成器函数,它会返回一个生成器
    2. 然后执行resource = acquire_resource,并在yield的地方状态被挂起这个生成器函数
    3. 在yield挂起的时候,with语句块中的语句开始执行
    4. 当with语句块退出的时候,yield被恢复,于是继续执行生成器函数中后面的语句

    如果在with语句块中发生了异常且未处理,则会在生成器yield被恢复的时候再次抛出这个异常。因此,可以使用try...except...finally语句来捕获可能存在的错误,以便保证yield后面的语句(通常是资源释放类的善后工作)可以正常执行。

  • 相关阅读:
    rosservice call ERROR:Unable to load type ... Have you typed 'make'
    查看ubuntu版本号
    VirtualBox虚拟机 host/guest 拷贝粘贴,共享剪贴板,安装guest additions
    traffic_light_bag_file 数据集 下载链接
    ubuntu linux double tab
    ubuntu linux查看cpu信息
    ubuntu linux查看硬盘使用量
    ubuntu查看nvidia显卡状态
    ibus
    windows7 屏幕亮图胡乱变化
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/10111121.html
Copyright © 2020-2023  润新知