当前位置:   article > 正文

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

contextlib.closing

简单介绍下我认识contextlib的过程吧,觉得这个内置lib还挺有意思的。

1、

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

2、

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

  1. with xxx as conn1:
  2. with yyy as conn2:
  3. code

真是蠢如老狗呀,其实可以:

  1. with xxx as conn1, yyy as conn2:
  2. code

3、

总感觉离开了with block,语句体的资源(文件啊,数据库连接啊,网络请求呀)就会自动关闭。

可是有一天我看到contextlib.closing()。 一脸懵逼,有了with还要这个干嘛,这是我内心真实OS。。

  1. from contextlib import closing
  2. from urllib2 import urlopen
  3. with closing(urlopen('http://www.python.org';)) as page:
  4. for line in page:
  5. print(line)

先来否定我的想法,凡用with就万事大吉,自动帮我关闭。

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

结果:

  1. # 报错:
  2. Traceback (most recent call last):
  3. File "1.py", line 38, in <module>
  4. with Door() as d:
  5. AttributeError: __exit__

果然呢,因为with语句体执行之前运行__enter__方法,在with语句体执行完后运行__exit__方法。
如果一个类如Door连这两个方法都没有,是没资格使用with的。 

4、
好吧,正式认识下contextlib:https://docs.python.org/dev/library/contextlib.html
有些类,并没有上述的两个方法,但是有close(),能不能在不加代码的情况下,使用with呢?
可以: 

  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()

结果:

  1. Door is opened
  2. Door is closed

contextlib.closing(xxx),原理如下:

  1. class closing(object):
  2. """Context to automatically close something at the end of a block.
  3. Code like this:
  4. with closing(<module>.open(<arguments>)) as f:
  5. <block>
  6. is equivalent to this:
  7. f = <module>.open(<arguments>)
  8. try:
  9. <block>
  10. finally:
  11. f.close()
  12. """
  13. def __init__(self, thing):
  14. self.thing = thing
  15. def __enter__(self):
  16. return self.thing
  17. def __exit__(self, *exc_info):
  18. self.thing.close()

这个contextlib.closing()会帮它加上__enter__()和__exit__(),使其满足with的条件。

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

!!!这个func()必须是生成器…
yield前半段用来表示__enter__()
yield后半段用来表示__exit__() 

  1. from contextlib import contextmanager
  2. @contextmanager
  3. def tag(name):
  4. print("<%s>" % name)
  5. yield
  6. print("</%s>" % name)
  7. with tag("h1"):
  8. print 'hello world!'

结果:

  1. <h1>
  2. hello world!
  3. </h1>

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

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

装饰器版本: 

  1. import time
  2. def wrapper(func):
  3. def new_func(*args, **kwargs):
  4. t1 = time.time()
  5. ret = func(*args, **kwargs)
  6. t2 = time.time()
  7. print 'cost time=', (t2-t1)
  8. return ret
  9. return new_func
  10. @wrapper
  11. def hello(a,b):
  12. time.sleep(1)
  13. print 'a + b = ', a+b
  14. hello(100,200)

结果:

  1. a + b = 300
  2. cost time= 1.00243401527

contextmanger版本:

  1. from contextlib import contextmanager
  2. @contextmanager
  3. def cost_time():
  4. t1 = time.time()
  5. yield
  6. t2 = time.time()
  7. print 'cost time=',t2-t1
  8. with cost_time():
  9. time.sleep(1)
  10. a = 100
  11. b = 200
  12. print 'a + b = ', a + b

结果:

  1. a + b = 300
  2. 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的效果,完美。 

源码:

  1. class GeneratorContextManager(object):
  2. """Helper for @contextmanager decorator."""
  3. def __init__(self, gen):
  4. self.gen = gen
  5. def __enter__(self):
  6. try:
  7. return self.gen.next()
  8. except StopIteration:
  9. raise RuntimeError("generator didn't yield")
  10. def __exit__(self, type, value, traceback):
  11. if type is None:
  12. try:
  13. self.gen.next()
  14. except StopIteration:
  15. return
  16. else:
  17. raise RuntimeError("generator didn't stop")
  18. else:
  19. if value is None:
  20. # Need to force instantiation so we can reliably
  21. # tell if we get the same exception back
  22. value = type()
  23. try:
  24. self.gen.throw(type, value, traceback)
  25. raise RuntimeError("generator didn't stop after throw()")
  26. except StopIteration, exc:
  27. return exc is not value
  28. except:
  29. if sys.exc_info()[1] is not value:
  30. raise
  31. def contextmanager(func):
  32. @wraps(func)
  33. def helper(*args, **kwds):
  34. return GeneratorContextManager(func(*args, **kwds))
  35. return helper

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/130288
推荐阅读
相关标签
  

闽ICP备14008679号