Python with和contextlib.closing配合使用(contextlib)

简单介绍下我认识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 closing
from urllib2 import urlopen
 
with 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 opened
Door 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
 
@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)
 
with tag("h1"):
    print 'hello world!'

结果:

<h1>
hello world!
</h1>

Wow,这个还真的挺酷的,以后可以用这个contextmanager来实现一些装饰器才能做的事,

比如给一段代码加时间cost计算:

装饰器版本: 

import time
def 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
 
@wrapper
def hello(a,b):
    time.sleep(1)
    print 'a + b = ', a+b
 
hello(100,200)

结果:

a + b =  300
cost time= 1.00243401527

contextmanger版本:

from contextlib import contextmanager
 
@contextmanager
def cost_time():
    t1 = time.time()
    yield
    t2 = time.time()
    print 'cost time=',t2-t1
 
with cost_time():
    time.sleep(1)
    a = 100
    b = 200
    print 'a + b = ', a + b

结果:

a + b =  300
cost 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:
                    raise
 
 
def contextmanager(func):
    @wraps(func)
    def helper(*args, **kwds):
        return GeneratorContextManager(func(*args, **kwds))
    return helper

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨痕诉清风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值