python 初次认识with 和 contextlib.closing

来源:互联网 发布:淘宝注册网店的网址 编辑:程序博客网 时间:2024/05/21 09:51
简单介绍下我认识contextlib的过程吧,觉得这个内置lib还挺有意思的。

1、
之前的我,只知道with会用来关闭文件,数据库资源,这很好。
只要实现了__enter__() 和 __exit__()这两个方法的类都可以轻松创建上下文管理器,就能使用with。

2、

我打开两个数据库的时候,都是 

with xxx as conn1:    with yyy as conn2:        code
真是蠢如老狗呀,其实可以:

with xxx as conn1, yyy as conn2:    code

3、
总感觉离开了with block,语句体的资源(文件啊,数据库连接啊,网络请求呀)就会自动关闭。
可是有一天我看到contextlib.closing()。 一脸懵逼,有了with还要这个干嘛,这是我内心真实OS。。
from contextlib import closingfrom urllib2 import urlopenwith closing(urlopen('http://www.python.org';)) as page:    for line in page:        print(line)
先来否定我的想法,凡用with就万事大吉,自动帮我关闭。
class Door(object):    def open(self):        print 'Door is opened'    def close(self):        print 'Door is closed'with Door() as d:    d.open()
结果:
# 报错:Traceback (most recent call last):  File "1.py", line 38, in <module>    with Door() as d:AttributeError: __exit__
果然呢,因为with语句体执行之前运行__enter__方法,在with语句体执行完后运行__exit__方法。
如果一个类如Door连这两个方法都没有,是没资格使用with的。 

4、
好吧,正式认识下contextlib:https://docs.python.org/dev/library/contextlib.html
有些类,并没有上述的两个方法,但是有close(),能不能在不加代码的情况下,使用with呢?
可以: 
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()
结果:
Door is openedDoor is closed
contextlib.closing(xxx),原理如下:
class closing(object):    """Context to automatically close something at the end of a block.    Code like this:        with closing(<module>.open(<arguments>)) as f:            <block>    is equivalent to this:        f = <module>.open(<arguments>)        try:            <block>        finally:            f.close()    """    def __init__(self, thing):        self.thing = thing    def __enter__(self):        return self.thing    def __exit__(self, *exc_info):        self.thing.close()
这个contextlib.closing()会帮它加上__enter__()和__exit__(),使其满足with的条件。

5、
是不是只有类才能享受with的便利呀? 我单单一个方法行不行?
行!既然认识了contextlib.closing(),必须认识下contextlib.contextmanager
这是一个装饰器,可以让一个func()变成一个满足with条件的类实例… 

!!!这个func()必须是生成器…
yield前半段用来表示__enter__()
yield后半段用来表示__exit__() 
from contextlib import contextmanager@contextmanagerdef tag(name):    print("<%s>" % name)    yield    print("</%s>" % name)with tag("h1"):    print 'hello world!'
结果:

<h1>hello world!</h1>

Wow,这个还真的挺酷的,以后可以用这个contextmanager来实现一些装饰器才能做的事,
比如给一段代码加时间cost计算:
装饰器版本: 
import timedef wrapper(func):    def new_func(*args, **kwargs):        t1 = time.time()        ret = func(*args, **kwargs)        t2 = time.time()        print 'cost time=', (t2-t1)        return ret    return new_func@wrapperdef hello(a,b):    time.sleep(1)    print 'a + b = ', a+bhello(100,200)
结果:

a + b =  300cost time= 1.00243401527


contextmanger版本:
from contextlib import contextmanager@contextmanagerdef cost_time():    t1 = time.time()    yield    t2 = time.time()    print 'cost time=',t2-t1with cost_time():    time.sleep(1)    a = 100    b = 200    print 'a + b = ', a + b
结果:

a + b =  300cost time= 1.00032901764

当然还是用装饰器方便美观点啦~

这是contextmanager原理:
1、因为func()已经是个生成器了嘛,所以运行__enter__()的时候,contextmanager调用self.gen.next()会跑到func的yield处,停住挂起,这个时候已经有了t1=time.time()
2、然后运行with语句体里面的语句,也就是a+b=300
3、跑完后运行__exit__()的时候,contextmanager调用self.gen.next()会从func的yield的下一句开始一直到结束。这个时候有了t2=time.time(),t2-t1从而实现了统计cost_time的效果,完美。 
源码:

class GeneratorContextManager(object):    """Helper for @contextmanager decorator."""    def __init__(self, gen):        self.gen = gen    def __enter__(self):        try:            return self.gen.next()        except StopIteration:            raise RuntimeError("generator didn't yield")    def __exit__(self, type, value, traceback):        if type is None:            try:                self.gen.next()            except StopIteration:                return            else:                raise RuntimeError("generator didn't stop")        else:            if value is None:                # Need to force instantiation so we can reliably                # tell if we get the same exception back                value = type()            try:                self.gen.throw(type, value, traceback)                raise RuntimeError("generator didn't stop after throw()")            except StopIteration, exc:                return exc is not value            except:                if sys.exc_info()[1] is not value:                    raisedef contextmanager(func):    @wraps(func)    def helper(*args, **kwds):        return GeneratorContextManager(func(*args, **kwds))    return helper


以上





原创粉丝点击