首页 > 代码库 > python上下文管理器及with语句

python上下文管理器及with语句

with语句支持在一个叫上下文管理器的对象的控制下执行一系列语句,语法大概如下:

with context as var:    statements

其中的context必须是个上下文管理器,它实现了两个方法__enter__,__exit__。

1.需求是怎么产生的

在正常的管理各种系统资源(文件、锁定和连接),在涉及到异常时通常是个棘手的问题。异常很可能导致控制流跳过负责释放关键资源的语句。

看一段简单的文件写入代码:

filename = ‘my_file.txt‘ f = open(filename,‘w‘)  f.write(‘Hello ‘)  f.write(‘World‘)  f.close() 

如果发生了意外的情况,例如写入‘World‘时磁盘空间不足,就会抛出异常,那么close()语句将不会被执行。

一般的解决方案是使用try-finally语句:

try:    filename = ‘my_file.txt‘     f = open(filename,‘w‘)      f.write(‘Hello ‘)      f.write(‘World‘)finally:    f.close() 

但随着语句的增多,try-finally显然不够简洁,用with-as语句可以很简洁的实现以上功能:

with open(‘my_file‘,‘w‘) as f:    f.write(‘Hello ‘)    f.write(‘World‘)

这样不仅能处理出现异常的情况,而且还避免了在open一个文件后忘记了写close()方法的情况。

线程中的锁其实也实现了上下文管理协议:

import threadingwith threading.Lock():    # 关键部分      statements    # 关键部分结束

再也不怕忘记将锁释放了。

2.上下文管理器的实现

上下文管理器要实现__enter__和__exit__的特殊方法。

__enter__(self): 进入上下文管理器时调用此方法,其返回值将被放入with-as语句中as说明符指定的变量中。

__exit__(self,type,value,tb):离开上下文管理器调用此方法。如果有异常出现,type、value、tb分别为异常的类型、值和追踪信息。如果没有异常,

3个参数均设为None。此方法返回值为True或者False,分别指示被引发的异常得到了还是没有得到处理。如果返回False,引发的异常会被传递出上下文。

文件上下文管理协议大概是如下实现的:

class OpenFile(object):    def __init__(self,filename,mode):        self.filename=filename        self.mode=mode    def __enter__(self):        self.f=open(self.filename,self.mode)        return self.f  #作为as说明符指定的变量的值    def __exit__(self,type,value,tb):        self.f.close()        return False   #异常会被传递出上下文with OpenFile(‘my_file.txt‘,‘w‘) as f:    f.write(‘Hello ‘)    f.write(‘World‘)

  相当于

try :    执行__enter__的内容finally:    执行__exit__的内容

3.异常的处理 

__exit__函数就能够拿到关于异常的所有信息(异常类型,异常值以及异常追踪信息),这些信息将帮助异常处理操作。

class ListTrans(object):    def __init__(self,alist):        self.alist=alist    def __enter__(self):        self.thecopy=list(self.alist)        return self.thecopy    def __exit__(self,exc_type,value,tb):        if exc_type is None:            self.alist[:]=self.thecopy        return False

没有异常发生时:

alist=[]with ListTrans(alist) as working:    working.append(1)    working.append(2)print alist

生成:

[1, 2]

有异常发生时:

alist=[]with ListTrans(alist) as working:    working.append(1)    working.append(2)    raise RuntimeError(‘we are hosed‘)print alist

生成:

Traceback (most recent call last):  File "C:/Python27/contextmanager.py", line 15, in <module>    raise RuntimeError(‘we are hosed‘)RuntimeError: we are hosed

alist无变化。

可以捕捉异常:

alist=[]try:    with ListTrans(alist) as working:        working.append(1)        working.append(2)        raise RuntimeError(‘we are hosed‘)except RuntimeError as e:    print eprint alist

生成:

we are hosed[]

当然,也可以简单的将__exit__的返回值设为True来忽略异常。

4.contextmanager装饰器

contextlib模块的contextmanager装饰器可以更方便的实现上下文管理器。

任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看做在代码块执行前的操作,

而任何yield之后的操作都可以放在exit函数中。

from contextlib import contextmanager@contextmanagerdef listTrans(alist):    thecopy=list(alist)    yield thecopy    alist[:]=thecopyalist=[]with listTrans(alist) as working:    working.append(1)    working.append(2)print alist

yield返回的值相当于__enter__的返回值。

要注意的是,这不是异常安全的写法,也就是说,当出现异常时,yield后的语句是不会执行的,想要异常安全,可用try捕捉异常:

from contextlib import contextmanager@contextmanagerdef listTrans(alist):    thecopy=list(alist)    try:        yield thecopy    except RuntimeError:        pass    alist[:]=thecopyalist=[]with listTrans(alist) as working:    working.append(1)    working.append(2)    raise RuntimeError

contextlib模块还有两个好玩的方法:nested,closing。

nested:用来更方便的减少嵌套写法:

开始是这么样的:

with open(‘toReadFile‘, ‘r‘) as reader:      with open(‘toWriteFile‘, ‘w‘) as writer:          writer.writer(reader.read())

用nested:

with contextlib.nested(open(‘fileToRead.txt‘, ‘r‘),                         open(‘fileToWrite.txt‘, ‘w‘)) as (reader, writer):      writer.write(reader.read()) 

python2.7后可以:

with open(‘fileToRead.txt‘, ‘r‘) as reader,open(‘fileToWrite.txt‘, ‘w‘) as writer:          writer.write(reader.read()) 

closing(object):创建上下文管理器,在执行过程离开with语句时自动执行object.close():

class Door(object) :    def open(self) :        print ‘Door is opened‘    def close(self) :        print ‘Door is closed‘with contextlib.closing(Door()) as door :    door.open()