簡單介紹下我認識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原理:
- 因爲func()已經是個生成器了嘛,所以運行__enter__()的時候,contextmanager調用self.gen.next()會跑到func的yield處,停住掛起,這個時候已經有了t1=time.time()
- 然後運行with語句體裏面的語句,也就是a+b=300
- 跑完後運行__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