环境管理器with

What

PEP(python2.6)中有个新的语法糖with用来简便的用于try/finally语法。类型contextmanager有两个方法__enter____exit__, 这两个方法如其名,被调用在进入with和离开with作用域的时候。

注意,无论是否异常__exit__都会被调用.

最常见的代码:

with open(filename) as f:
    for i in f:
        print i

直观的感受就是不需要手动调用close关闭,会在离开with作用域的时候自动调用。有点类似C++对象管理资源的概念(后面就有个类似auto_ptr的类库)。

with还可以嵌套的

with A() as a, B() as b:
        suite

等价于:

with A() as a: 
    with B() as b: 
        suite

来个例子,读取A文件内容另存到B文件,是不是很方便?

with open(A) as a, open(B, 'w') as b:
    b.write(a.read())

How

定义

  • contextmanager.__enter__()
    进入运行时的with作用域是调用,返回 as使用的对象

  • contextmanager.__exit__(*exc_type*, *exc_val*, *exc_tb*)
    离开运行时的with作用域是调用,返回一个boolean 标志,表明发生的任何异常是否被压制。如果有异常发生在with作用域,exc_type, exc_val, exc_tb包含异常信息,如果没有异常发生,三个参数均为None。如果返回True压制with作用域发生的异常,继续执行with,返回False异常会在这个函数结束后继续传播。

来个例子, 实现个普遍适用的管理器,省去每次使用都需要定义一个类

class GeneratorContextManager(object):
    def __init__(self, gen):
        # 保存generator对象
        self.gen = gen

    def __enter__(self):
        # 返回`as`引用的对象
        return self.gen.next()

    def __exit__(self, type, value, traceback):
        try:
            # return False, 并且完成调用
            self.gen.next()
        except StopIteration:
            # 捕获StopIteration,迭代结束的异常,无需抛出
            pass

def contextmanager(func):
    # 定义装饰器
    def _f():
       return GeneratorContextManager(func())
    return _f

@contextmanager
def fun():
    # 被装饰的函数必须使用yield,或者说必须是一个迭代器,并且只有一次迭代
    f = open('run.py')
    # yield 对象为as使用的对象
    yield f
    f.close()

with fun() as f:
    # 使用方法
    print f

模拟contextlib中的方法,实现普遍适用的环境管理器,迭代器是为了实现goto方法。(看过tornado协程的同学一定会觉得亲切,后面有空再写一遍专门介绍迭代器)

Why

前面说到with是个语法糖,简化try/finally的操作。通俗点说就是为了写代码的时候少写点下面这样的代码

try:
    f = open(filenmae)
finally:
    f.close()

而且防止很多粗心鬼忘记写上面的代码因为有点繁琐,写成

with open(filename) as f:
    dosmth

既简单又简洁,提供程序健壮,防止资源泄漏

工具类

前面提到python提供了一个类来辅助with(类似auto_ptr的类库)
就是contextlib

contextlib

这个模块是标准库提供的环境管理器方面的工具类

  • contextlib.contextmanager(func)
    这是个装饰器,用来生成context managers对象,不需要生成class或者单独的__enter____exit__方法
    一个简单的例子(和上面写的那个例子差不多似乎不是那么好用,但也不难用)
      from contextlib import contextmanager
    
      @contextmanager
      def tag(name):
          print "<%s>" % name
          yield
          print "" % name

      >>> with tag("h1"):
      ...    print "foo"
      ...
      

foo

  • contextlib.nested(mgr1[, mgr2[, ...]])
    组合多个context managers对象到一个单独的nested context manager对象
    简单示例:
from contextlib import nested
with nested(*managers): 
    do_something()

python2.7后弃用,有语法可以实现

with A() as a, B() as b:
    do_something()
  • contextlib.closing(thing)
    返回Context Manager Type对象,thing需要有close函数
    等同于下面的实现:
    from contextlib import contextmanager

    @contextmanager
    def closing(thing): 
        try: 
            yield thing 
        finally: 
            thing.close()
    

    例子如下:

    from contextlib import closing
    import urllib
    
    with closing(urllib.urlopen('http://www.python.org')) as page: 
        for line in page: 
            print line
    

不需要显式的调用page.close(),即使是发生了错误,当离开with作用域的时候会自动调用page.close()。这个算得上是懒人的利器了。java里叫支持closable接口

End

你可能感兴趣的:(环境管理器with)