Python

[Effective Python] 項目43:contexlibとwith文をtry/finallyの代わりに考える

with文とは

Pythonのwith文とは、
コードが 特別なcontext下で 実行されていることを示すのに使われてます。

良く使う例

ファイルを開いてデータを書き込む例。

with open('file.txt', 'w') as handle:
    handle.write('some data')

ファイルにデータを書き込んだ後(withブロックを抜けた後)で、
ファイルのHanlderを閉じてくれます

try/finally 構成でやると、こうなイメージになります。

try: 
    hanlder = open('file.txt', 'w')
    handle.write('some data')
finally:
    handler.close()

with構文をネスト

with open("sample1.txt", "r") as f1:
    with open("sample2.txt", "w") as f2:
        f2.write(f1.read())

with構文に対応したクラスを作ってみよう

with構文が使えるクラスを作成するには、

enter()メソッドと exit()メソッドの両方を定義しなければなりません。

# My with sample class

class MyWithSampleClass:
    def __enter__(self):
        print('START')
        return self

    def myfunc(self):
        print('Do something...')

    def __exit__(self, exception_type, exception_value, traceback):
        print('END')


with MyWithSampleClass() as c:
    c.myfunc()

with構文に対応した関数を作ってみよう

  • __enter____exit__を書きたくない
  • クラスを作りたくない

そうな時には、 contextlibモジュール の contextmanager デコレータを使います。

例えば、あるフォルダーに入り処理を行ったあとにまた入る前のディレクトリに切り替えたい
ユースケースがあるとします。

cwd = os.getcwd()
os.chdir('/tmp/foo')
try:
    run()  # 何か処理
finally:
    os.chdir(cwd)

withで使えるように次のようなcdを実装して:

@contextlib.contextmanager
def cd(target_dir):
    cwd = os.getcwd()
    try:
        os.chdir(target_dir)
        yield
    finally:
        os.chdir(cwd)

以下のように使えます:

with cd('/tmp/foo'):
    run()  # 何か処理

まとめ

  • 定型処理にwithを使うことでコードが簡潔化
  • ネストも可能で複雑な処理がさらに見やすくなる
  • with構文に対応したクラスも関数も