Pythonのwithステートメントのまとめ

Pythonを使う上で、withステートメントは、やはり見逃せない機能の1つであると思います。

Python2.5〜3.xまで網羅しているようにしたつもりです。

基本

まず、一番身近なファイル操作の例を載せておきます。

with open("...") as f:
    print(f.read())

これは、以下と同等です。

f = open("...")
print(f.read())
f.close()

withステートメントを使うと、withを抜けた時に、自動的にf.closeされます。

asは必須ではありません。

f = open("...")
with f:
    print(f.read())

これも先ほどのコードと同等です。

withに対応したクラスを作る

使うだけでは理解も深まらないので、作る方も見てみましょう。

withに渡すのは__enter____exit__の2つのメソッドを持ったクラスのインスタンスです。

class Test:
    def __init__(self, hoge):
        print("init")
        self.hoge = hoge
    
    def printHoge(self):
        print(self.hoge)

    def __enter__(self):
        print("enter")
        return self
    
    def __exit__(self, type, value, traceback):
        print("exit")


with Test("hoge") as t:
    t.printHoge()

出力

init
enter
hoge
exit

withステートメントに入る時にTest#__enter__が、出る時にTest#__exit__が呼ばれます。asで指定する変数には、Test#__enter__の戻り値が入るので、上記のサンプルでreturn selfしているところがミソです。

__exit__の引数は、with内部で例外が発生した時以外はNoneのようです*1

__exit__を上手く使っている良い実例を見つけました → Pythonのwith構文で例外を補足する実例 - $ cat /var/log/shin

Tips

複数のwith

以下のように、複数のファイルを同時に開いて何かしたいケースが有るかもしれません。

with open("...") as f1:
    with open("...") as f2:
        # ...

この時、Python 2.7以降ではカンマ区切りで複数指定することが可能です。

with open("...") as f1, open("...") as f2:
    # ...

ちなみに、Python 2.7未満ではcontextlib.nestedを使う方法があるようです。

# 2.7以降では非推奨
import contextlib
with contextlib.nested(open("..."), open("...")) as (f1, f2):
    # ...

withステートメントから抜ける

withステートメントを途中で抜ける構文はありません*2

  • withを関数で括ってreturnする
  • 素直にif文を使って書く

といった方法を使うしかないようです*3

*1:この例外ハンドリングを活用できるケースは自分にはパット浮かびませんが、場合によっては巧い時があるのだと思う

*2:breakを使ってもダメです

*3:Rubyのブロックが恋しい