contextmanager
内部DSLパターン
でも書きましたが、Pythonでもwith文を使えば、Rubyのブロックと似たような事が出来ます。
しかし、with文コンテキストマネージャには、__enter__、__exit__メソッドを定義しなければならないのが面倒です。
そこで、contextlib.contextmanagerデコーレータを使います。
from __future__ import with_statement from contextlib import contextmanager import time from datetime import datetime @contextmanager def stopwatch(): print "計測開始 %s"%(datetime.now()) begin = time.clock() try: yield finally: end = time.clock() print "計測終了 %s"%(datetime.now()) print "所要時間 = %s"%(end - begin) def fib(n): #計算に時間がかかる関数 if n == 0 or n == 1: return 1 else: return fib(n - 1) + fib(n - 2) with stopwatch(): for i in xrange(30): print i, fib(i)
contextmanagerデコレータを使える条件は、yieldが1回だけのジェネレータ関数であること。
0回でも2回でもダメ。
今回の例ではyieldが返す値は空ですが、何らかの値を返すことも出来て、
その値が、with spam() as egg で egg に代入されます。
ブロックの実行中に例外が投げられると、yieldの書かれた所に再送出されます。
つまり、ブロック実行後の処理(今回はprint "所要時間 = %s"%(end - begin)等)を確実に実行させるためには、
try 〜 finally 〜でくくる必要があります。
もちろん、try〜except〜で、例外を補足したり、再送出する事もできます。
ちなみに、contextlibモジュールには、nestedとclosingという関数も用意されています。
closing() は、単にブロックの終了時にcloseメソッドを呼びます。
from __future__ import with_statement from contextlib import closing with closing(urllib.urlopen('http://www.python.org')) as page: for line in page: print line # これと同等 page = closing(urllib.urlopen('http://www.python.org') try: for line in page: print line finally: page.close()
nested は with に コンテキストマネージャ を複数指定したいときに使います。
from contextlib import nested with nested(A, B, C) as (X, Y, Z): do_something() #これと同等 with A as X: with B as Y: with C as Z: do_something()
ちなみにpython 3.1 では、新しい文法が追加されてnestedは非推奨になっています。
python 2.7でも使えるのかは知りませんが、2.7はまだα版みたいです。
with open('mylog.txt') as infile, open('a.out', 'w') as outfile: for line in infile: if '<critical>' in line: outfile.write(line)