iMind Developers Blog

iMind開発者ブログ

Pythonのcontextlibでwithに渡せる処理を定義する

概要

Pythonのwith構文で自動でリソースがcloseされる系の処理を、contextlibを利用して定義してみる。

バージョン情報

  • Python 3.7.3

contextlib2について

contextlibは古いバージョンでは入っていないこともあるので、そうしたバージョンでも使えるようにcontextlib2というライブラリが用意されている。

複数のバージョンから利用される可能性があるコードを書く際は、バージョンの違いでいらぬトラブルを招かないようにこちらが採用されていることが多い。

contextmanagerで自動でファイルをclose

contextlibを利用しない場合が __enter__ や __exit__ を実装したクラスを用意してwithに渡す。

class AutoCloseFile:
     def __init__(self, path):
         self.path = path

     def __enter__(self):
         self.f = open(self.path)
         return self.f

     def __exit__(self,  exception_type, exception_value, traceback):
         self.f.close()

# 上のクラスを使ってファイルを読む
with AutoCloseFile('foo.txt') as f:
    print(f.read())

# 閉じれてる?
print(f.closed)
    #=> True

上記と同じ処理がcontextlibのcontextmanagerでは下記のように書ける。

import contextlib

@contextlib.contextmanager
def auto_close_file(path):
    try:
        f = open(path)
        yield f
    finally:
        f.close()

# 上の関数を使ってファイルを読む
with auto_close_file('foo.txt') as f:
    print(f.read())

# 閉じれてる?
print(f.closed)
    #=> True

try 〜 finallyしながらリソースを確保して、yieldでwith statementにリソースを渡す構造。

closingでもう少し簡略に

contextlib.closingを使うと、try〜finalyしてcloseを呼び出すところまでやってくれる。

import contextlib

with contextlib.closing(open('foo.txt')) as f:
    print(f.read())

print(f.closed)
    #=> True

closingはclose関数が実装されていればなんでも引数に取れる。

# closeが定義されているclass
class Foo:
    def close(self):
        print('close')

# 実行するとcloseが呼び出される
with contextlib.closing(Foo()) as f:
    pass

suppressでエラーを無視した処理

contextlibにはsuppressという例外を握りつぶす機能もある。

例えば下記はFileNotFoundErrorを握りつぶしつつファイルを読んでいる。

with contextlib.suppress(FileNotFoundError):
    with contextlib.closing(open('bar.txt')) as f:
        print(f.read())

bar.txtが存在しない場合は、例外が無視されて何も起きずに処理が終わる。

ExitStackで複数のファイルをclose

ExitStackは __exit__ を呼び出す必要があるリソースをstackに積んでおいてスコープが終わったらすべて __exit__ して回る。

たとえば3つのファイルに文字列を出力をするようなコードの場合。

with contextlib.ExitStack() as stack:
    foo = stack.enter_context(open('foo.txt', 'wt'))
    bar = stack.enter_context(open('bar.txt', 'wt'))
    baz = stack.enter_context(open('baz.txt', 'wt'))

    foo.write('foo')
    bar.write('bar')
    baz.write('baz')

print(foo.closed)
    #=> True

このようにExitStackにenter_contextで積んでいけば最後にまとめて __exit__ してくれる。

改定履歴

Author: Masato Watanabe, Date: 2019-07-06, 記事投稿