python上下文管理器及with语句

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

1
2
with context as var:
     statements

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

1.需求是怎么产生的

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

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

1
2
3
4
5
filename  =  'my_file.txt'
=  open (filename, 'w'
f.write( 'Hello '
f.write( 'World'
f.close()

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

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

1
2
3
4
5
6
7
try :
     filename  =  'my_file.txt'
     =  open (filename, 'w'
     f.write( 'Hello '
     f.write( 'World' )
finally :
     f.close()

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

1
2
3
with  open ( 'my_file' , 'w' ) as f:
     f.write( 'Hello ' )
     f.write( 'World' )

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

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

1
2
3
4
5
import  threading
with threading.Lock():
     # 关键部分 
     statements
     # 关键部分结束

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

2.上下文管理器的实现

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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' )

  相当于

1
2
3
4
try 
     执行__enter__的内容
finally
     执行__exit__的内容

3.异常的处理 

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

1
2
3
4
5
6
7
8
9
10
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

没有异常发生时:

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

生成:

1
[ 1 2 ]

有异常发生时:

1
2
3
4
5
6
alist = []
with ListTrans(alist) as working:
     working.append( 1 )
     working.append( 2 )
     raise  RuntimeError( 'we are hosed' )
print  alist

生成:

1
RuntimeError: we are hosed

alist无变化。

可以捕捉异常:

1
2
3
4
5
6
7
8
9
alist = []
try :
     with ListTrans(alist) as working:
         working.append( 1 )
         working.append( 2 )
         raise  RuntimeError( 'we are hosed' )
except  RuntimeError as e:
     print  e
print  alist

生成:

1
2
we are hosed
[]

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

4.contextmanager装饰器

@contextmanager

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

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

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

1
2
3
4
5
6
7
8
9
10
11
from  contextlib  import  contextmanager
@contextmanager
def  listTrans(alist):
     thecopy = list (alist)
     yield  thecopy
     alist[:] = thecopy
alist = []
with listTrans(alist) as working:
     working.append( 1 )
     working.append( 2 )
print  alist

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from  contextlib  import  contextmanager
@contextmanager
def  listTrans(alist):
     thecopy = list (alist)
     try :
         yield  thecopy
     except  RuntimeError:
         pass
     alist[:] = thecopy
alist = []
with listTrans(alist) as working:
     working.append( 1 )
     working.append( 2 )
     raise  RuntimeError

nested与closing

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

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

当要嵌套的写上下文管理器时:

1
2
3
with  open ( 'toReadFile' 'r' ) as reader: 
     with  open ( 'toWriteFile' 'w' ) as writer: 
         writer.writer(reader.read())

可以用nested简化写法:

1
2
3
with contextlib.nested( open ( 'fileToRead.txt' 'r' ), 
                        open ( 'fileToWrite.txt' 'w' )) as (reader, writer): 
     writer.write(reader.read())

python2.7后nested就过时了:

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

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

1
2
3
4
5
6
7
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 ()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值