pythonのcontextlibでwith文を活用したい
pythonにはcontextlibってライブラリがあるそうです。
普通にwith文に対応したものを作ろうとすると__enter__
と__exit__
を実装しなければならないわけですが、これが結構面倒くさいんですよね。
で、これを手軽にしてくれるのがcontextlibというわけ。
余談ですが、with文で使えるオブジェクトのことをコンテキストマネージャ型っていうらしいです。知らなかった…。
コンテキストマネージャを作ってみる
一番手軽な使い方は以下のような感じ。
>>> import contextlib
>>> @contextlib.contextmanager
... def test(x):
... print('started', x)
... yield
... print('ended', x)
...
>>> with test('hoge'):
... print('inside')
...
started hoge
inside
ended hoge
yield
の前が前処理、後が後処理。まあまあ分かりやすいですね?
yieldで値を返すことも出来て、以下のように使います。
>>> import contextlib
>>> @contextlib.contextmanager
... def test(x):
... print('started', x)
... yield x
... print('ended', x)
...
>>> with test('huga') as value:
... print('inside', value)
...
started huga
inside huga
ended huga
こんな感じ。yield
で返したものはas
で受け取れます。
以下のような感じで後処理を書くときとかに使うみたい。
>>> import contextlib
>>> @contextlib.contextmanager
... def auto_close(f):
... try:
... yield f
... finally:
... f.close()
...
ちなみに、この例と同じ機能を持つ関数contextlib.closing
が提供されています。一々定義しなくて良いならそっちのが良いね。
例外を無視する
例外を無視するためにも使えるようです。以下のような感じ。
>>> import contextlib
>>> open('not found', 'r')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'not found'
>>> with contextlib.suppress(FileNotFoundError):
... open('not found', 'r')
...
# 何も起こらない。
>>> try:
... open('not found', 'r')
... except FileNotFoundError:
... pass
...
# さっきと同じ。
下がtry-exceptを使った場合、上がcontextlibを使った場合。ちょっと見やすくて良いかも。
ちなみに、contextlib.suppress
には複数の引数を渡す事が可能で、複数の例外を無視することも可能です。
あたりまえですが、Exception
を渡せばあらゆる例外を無視出来ます。あまり、いや全くおすすめしませんが。
標準出力/標準エラー出力をファイルにリダイレクトする
標準出力に何かを吐く関数があったとして、その出力を拾いたいとします。どんな状況だかよく分からないけれど、あったとします。
>>> import contexlib
>>> import io
>>> f = io.StringIO()
>>> with contextlib.redirect_stdout(f):
... print('hello, world!')
...
>>> f.getvalue()
'hello, world!\n'
これで標準出力を取得出来ます。便利…か?
ちなみに、redirect_stdout
をredirect_stderr
に変えると標準エラー出力を取れます。
os.system
の出力を取得出来たら便利かと思ったのですが、残念ながらそれは無理なようです。
参考: 29.6. contextlib — with 文コンテキスト用ユーティリティ — Python 3.4.3 ドキュメント