Blanktar

  1. top
  2. blog
  3. 2015
  4. 10

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_stdoutredirect_stderrに変えると標準エラー出力を取れます。

os.systemの出力を取得出来たら便利かと思ったのですが、残念ながらそれは無理なようです。

参考: 29.6. contextlib — with 文コンテキスト用ユーティリティ Python 3.4.3 ドキュメント