每日一记:(2016-12-02)Python:ContextManager(上下文管理器)

今天的日志就来说说python的一个优雅的语法糖——ContextManager(上下文管理器)

1. 那么什么叫ContextManager

上下文管理器的任务是 – 代码块执行前准备,代码块执行后收拾。其中代码块执行后包括了正常退出和产生异常。
当然这些功能可以用try-except语句实现,但是如果使用try-except代码就显得不那么优雅。而且经常会忘记会产生异常这种情况。

2. 如何使用上下文管理器?

先给个例子吧!
 
 
  1. >>> with open('test.txt', 'w') as writer:
  2. ...     writer.write("aaaa")
  3. ...     writer.write('bbbb')
其实就是with语句啦,使用的时候也是很简单的说。当然也可以没有as之后的内容单独一个with open()也是可以的。

3. 自定义上下文管理器

要实现上下文管理器,必须实现两个方法 – 一个负责进入语句块的准备操作,另一个负责离开语句块的善后操作。
要实现上下文管理器,需要实现两个魔法方法__enter__以及__exit__(双下划线作为前缀及后缀)。

当一个对象被用作上下文管理器时:

__enter__ 方法将在进入代码块前被调用。

__exit__ 方法则在离开代码块之后被调用(即使在代码块中遇到了异常)。

下面是上下文管理器的一个例子,我们通过例子看一下__enter__、__exit__都是什么时候被调用的吧

 
 
  1. >>> class PypixContextManagerDemo:
  2. ...     def __enter__(self):
  3. ...             print 'Entering the block'
  4. ...     def __exit__(self, *unseted):
  5. ...             print 'Exiting the block'
  6. ...
  7. >>> with PypixContextManagerDemo():
  8. ...     print 'In the block'
  9. ...
  10.   Entering the block
  11.   In the block
  12.   Exiting the block
这里有一个疑问:这上面都是对于类来说实现了上下文管理器,但是对于open(处理文件的函数)他就是一个函数,为啥也能用上下文管理器呢?
上面的例子还有几点不完善的地方:
  • 没有传递任何参数。
  • 在此没有使用“as”关键词。
  • 稍后我们将讨论__exit__方法的参数设置。
 
 
  1. ...     def __init__(self, filename, mode):
  2. ...             self.filename = filename
  3. ...             self.mode = mode
  4. ...     def __enter__(self):
  5. ...             self.openedFile = open(self.filename, self.mode)
  6. ...             return self.openedFile
  7. ...     def __exit__(self, *unseted):
  8. ...             self.openedFile.close()
  9. ...
  10. >>> with PypixOpen(filename, mode) as writer:
  11. ...     writer.write("Hello world from our new Context Manager")
那么,上面解决了输入参数和as之后赋值问题,还有一个异常处理的问题,还没有解决呢?

如何处理异常

如果语句块内部发生了异常,__exit__方法将被调用,而异常将会被重新抛出(re-raised)。当处理文件写入操作时,大部分时间你肯定不希望隐藏这些异常,所以这是可以的。而对于不希望重新抛出的异常,我们可以让__exit__方法简单的返回True来忽略语句块中发生的所有异常(大部分情况下这都不是明智之举)。
完备的__exit__函数签名应该是这样的:
def __exit__(self,exc_type,exc_val,exc_tb)
__exit__方法中给的参数有exc_type, exc_val, exc_tb,那么就可以进行异常处理了。(exc_type是异常类型,exc_val是异常的值和exc_tb是异常的回溯信息)
调用上下文管理器的 __exit__() 方法,如果 with结构中产生异常,那么该异常的 type、value 和 traceback 会作为参数传给 __exit__(),否则传三个 None

  • 如果 with_suite 产生异常,并且__exit__() 的返回值等于 False,那么这个异常将被重新抛出到上层
  • 如果 with_suite 产生异常,并且__exit__() 的返回值等于 True,那么这个异常就被忽略,继续执行后面的代码
通过上面的内容我们可以自己进行构造__exit__()函数了。
例:
 
 
  1. >>> def __exit__(self,exc_type,exc_val,exc_tb):
  2. ...     self.file.close()
  3. ...     if exc_type:
  4. ...             print exc_type, exc_val, exc_tb
  5. ...             return False
  6. ...     return True

4. contextmanager装饰器

@contextmanager

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

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

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

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

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

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

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

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

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

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

python2.7后nested就过时了:

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


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

 
 
  1. class Door(object) :
  2.     def open(self) :
  3.         print 'Door is opened'
  4.     def close(self) :
  5.         print 'Door is closed'
  6. with contextlib.closing(Door()) as door :
  7.     door.open()

5. 其他

Python定义了几个上下文管理器来支持简单的线程同步,提示文件或其他对象的关闭,以及更简单地操作主动小数算术上下文。

评论

还没有任何评论,你来说两句吧

你必须 登录 才能发表评论.

衫小寨 出品

"
Press Ctrl+C to copy the following code.