コンテキストマネージャ
ファイルをオープンするようなときは with文を使う方がよいと説明しました。
code: 0904_open_with_sample.py
with open("data.txt") as f:
data = f.read()
ファイルをオープンするときにメモリの枯渇やオープンできるファイル数の上限など、意図しないリソース制限でプログラムが異常終了することがあるためです。
処理の前後にあらかじめ定義しておいた処理を呼び出す機能を実現するものがコンテキストマネージャ(Context Manager) です。
まずは、コンテキストマネージャを作ってみましょう。
code: 0905_context_manager.py
class MyOpen:
def __init__(self, filename):
print("init() called")
self.filename = filename
def __enter__(self):
# with文のブロックの最初にコンテクストマネージャが実行すべき動作を定義
print("enter() called")
self.file = open(self.filename, "r")
return self.file
def __exit__(self, exc_type, exc_value, exc_traceback):
# ブロックの実行(または終了)後のコンテキストマネージャーの動作を定義
print("exit() called")
self.file.close()
with MyOpen("data.txt") as manager:
print("with statement block")
with文でMyOpenクラスのインスタンスオブジェクトmanagerを作成します。
これで、with文でのブロックで発生したエラーに対応することができるようになります。
コンテキストマネージャでの特殊メソッド
コンテキストマネージャーで使用される特殊メソッドには次のものがあります。
__enter__
__exit__
contextlib を使って簡単にコンテキストマネージャーを作る
先程の例のように、__enter__() と __exit__() のクラスメソッドを毎回定義するのが面倒な場合もあります。そうした場合は、モジュールcontextlibにあるデコレータcontextmanager を使うと、必要な処理だけを記述することができます。
code: 0906_context_manager_with_contextlib.py
from contextlib import contextmanager
@contextmanager
def MyOpen(filename):
print('contextmanager - method called')
file = open(filename, 'r')
yield file
print('contextmanager - exit called')
file.close()
with MyOpen('data.txt') as f:
f.read()
ただし、この場合いつ異常終了が発生するかコンテキストマネージャ側にはわからないので、
try/finally を記述しておく必要があります。
code: 0907_context_manager_with_contextlib_and_try_finaly.py
from contextlib import contextmanager
@contextmanager
def MyOpen(filename):
try:
file = open(filename, 'r')
yield file
finally:
file.close()
with MyOpen('data.txt') as f:
f.read()
contexlib の ContextDecoratorクラスを再定義する
モジュールcontextlibにあるデコレータcontextmanager を使うと__enter__() と __exit__() の特殊メソッドを毎回定義するの必要がないと説明しました。
これらの特殊メソッドを独自に作成したいときも、モジュールcontextlibのContextDecoratorクラスを継承して再定義する方が簡単です。(メソッドオーバーライドですよね)
code: 0908_context_manager_redefine_contextdecorator.py
from contextlib import ContextDecorator
class makeHtml(ContextDecorator):
def __enter__(self):
print('<html>')
print('<body>')
print('<p>')
return self
def __exit__(self, *exc):
print('</p>')
print('</body>')
print('</html>')
return False
@makeHtml()
def gen_html():
print('Here is simple text.')
gen_html()
例外の捕獲
例えば、ファイルを削除しようとするとき、既にファイルが存在しないと例外が発生します。
code: python
try:
os.remove('sample.txt')
except FileNotFoundError:
pass
これは、次のように記述することもできます。
code: python
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('sample.txt')
contextlib.supress()で指定された例外が with文のブロックで発生すると、それを抑制して、with文のブロックの続きから実行を再開するコンテキストマネージャを返します
python 3.10 からimportでの表記と同じように、丸括弧で複数のコンテキストマネージャを連結することができるようになりました。
参考