Python

Python の with 文のためのクラス

処理を終える際に実行すべき処理を書く構文が多くの言語にある。

Java なら try with resources。
C# なら using ステートメント。

Python には with ステートメントがある。

with ステートメントに供するクラスは2つのメソッド __enter____exit__ を用意する必要がある。

with + コンストラクタ

python
class WithClass:
  def __init__(self):
    print("init")

  def hoge(self):
    print("hoge")

  def __enter__(self):
    print("enter")
    return self

  def __exit__(self, ex_type, ex_value, trace):
    print("exit: ", ex_type, ex_value, trace)

with WithClass() as w:
  w.hoge()

こんな感じになる。

実行すると

init
enter
hoge
exit:  None None None

と出力される。

私が最初気づかなかったのは、 __enter__(self) の返戻値が w の値になるという点。
だから、 return self は必須。

with + 非コンストラクタ

もうひとつのパターンは、with open(filename) as f: のように、コンストラクタではないメソッドがある場合。

python
class WithClass:
  def __init__(self):
    print("init")

  def hoge(self):
    print("hoge")

  def __enter__(self):
    print("enter")
    return self

  def __exit__(self, ex_type, ex_value, trace):
    print("exit: ", ex_type, ex_value, trace)

def creator():
  return WithClass()

with creator() as w:
  w.hoge()

こんな具合。
このパターンでも __enter__(self) は必須。
wの値は __enter__(self) の返戻値になる。 create() の返戻値ではない。

というわけで、出力されるのは

init
enter
hoge
exit:  None None None

と、最初の例と同じになる。

__enter__(self) の返戻値を self 以外にしたくなる局面が思いつかないんだけど、きっとなにかあるんだろう。

例外が起きる場合

で。例外を起こしてみる:

python
class WithClass:
  def __init__(self):
    print("init")

  def hoge(self):
    print(1/0) # ゼロ除算!

  def __enter__(self):
    print("enter")
    return self

  def __exit__(self, ex_type, ex_value, trace):
    print("exit: ", ex_type, ex_value, trace)
    print("---")

with WithClass() as w:
  w.hoge()

すると、出力はこんな風に

init
enter
exit:  <class 'ZeroDivisionError'> division by zero <traceback object at 0x1092539c8>
---
Traceback (most recent call last):
  File "with3.py", line 16, in <module>
    w.hoge()
  File "with3.py", line 6, in hoge
    print(1/0)
ZeroDivisionError: division by zero

なる。
ここで、 __exit__(self,略)True を返すと例外を握りつぶしてくれる。

こんな具合:

python
class WithClass:
  def __init__(self):
    print("init")

  def hoge(self):
    print(1/0) # ゼロ除算!

  def __enter__(self):
    print("enter")
    return self

  def __exit__(self, ex_type, ex_value, trace):
    print("exit: ", ex_type, ex_value, trace)
    print("---")
    return True

with WithClass() as w:
  w.hoge()

出力は

init
enter
exit:  <class 'ZeroDivisionError'> division by zero <traceback object at 0x10885e9c8>
---

となっていて、例外が握りつぶされていることがわかる。

参考サイト:

http://shin.hateblo.jp/entry/2013/03/23/211750
http://reiki4040.hatenablog.com/entry/20130331/1364723288