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》

posted @ 2019-01-27 21:34  Sakura_lht  阅读(177)  评论(0编辑  收藏  举报