こんにちは、とりゅふです。みなさん、Pythonでファイルを読み込みする時って普段どのように書きますか?私ならこんな感じで書きます。
with open('text.csv') as f: text = f.read() print(text)
with
を使ってファイルを開き、中のテキストを取り出し、printするプログラムです。では、このwith
、何者か説明できますか?
今回はこのwith
構文についてまとめました。
本記事のコードはすべて、Python 3.7.6で実行しています。
with構文の正体
まずこのwith
構文の正体について見ていきましょう。with
は前処理と後処理を実行してくれる構文です。
前処理と後処理が必要な処理って必ずありますよね。例えばファイルのOpen、Closeだったり、データベースの接続、切断だったり、これらは主処理の前後に前処理と後処理が実行される処理になります。
例えば、with open('text.csv')
のように、ファイルの読み込みでwith
を使った場合、内部的には以下のような処理になります。
- ファイルをopenする
- ファイルの読み書きなどの主処理を実行する
- ファイルをcloseする
ではこれをwith
なしで書くとどうなるでしょうか?
f = open('text.csv') txt = f.read() print(txt) f.close()
open()
してread()
するまでは良いのですが、close()
するの忘れそうになりませんか?ファイルをcloseし忘れて、他の処理がファイルにアクセスできなくなったり、無駄なリソースを使い続けたままになったりと、バグの元になりがちですよね。
with
構文を使えば、このopen()
、close()
を意識することなく処理が書けるので、より品質の高いコードを書くことができるのです。
withを使えるクラス
with
構文で呼び出しができるクラスは、__enter__()
と、__exit__()
を実装したクラスになります。例えば以下のようなクラスです。
class WithSample: def __init__(self): print('初期化') def __enter__(self): print('前処理') return self def __exit__(self, exc_type, exc_value, traceback): print('後処理') def execute(self): print('主処理') if __name__ == '__main__': with WithSample() as w: w.execute()
初期化 前処理 主処理 後処理
まずはまずは通常のクラスのように、__init__()
が実行され、その次に前処理として__enter__()
が実行されます。ここでreturn
したオブジェクトが、with
構文のas
に当たるオブジェクトになります。ここではwith
構文内でWithSample
クラスの処理を実行したいので、self
を返すようにしています。
with
ブロック内で主処理を実行し、with
ブロックから抜ける時に、後処理として__exit__()
が実行されます。
ちなみにですが、__enter__()
と、__exit__()
のいずれかが実装されていないクラスをwith
構文で呼び出そうとすると、AttributeError
となります。出入口は必須ってことですね。
with構文の例外処理
次は例外発生時の検証として、以下のようなわり算だけができる電卓クラスを作り、わざとZeroDevisionError
を発生させてみます。
class Calculator: def __enter__(self): print('計算を開始します。') return self def __exit__(self, exc_type, exc_value, traceback): print('計算を終了します。') def devide(self, n, m): print('{} ÷ {} ='.format(n, m), end='') try: print(n / m) except Exception as e: print('[ERROR]') raise e if __name__ == '__main__': print('** [START] 1回目の計算機 **') with Calculator() as calc1: calc1.devide(10, 5) calc1.devide(10, 2) print('** [END] 1回目の計算機 **\n') print('** [START] 2回目の計算機 **') with Calculator() as calc2: calc2.devide(10, 4) calc2.devide(10, 0) calc2.devide(10, 3) print('** [END] 2回目の計算機 **\n')
** [START] 1回目の計算機 ** 計算を開始します。 10 ÷ 5 =2.0 10 ÷ 2 =5.0 計算を終了します。 ** [END] 1回目の計算機 ** ** [START] 2回目の計算機 ** 計算を開始します。 10 ÷ 4 =2.5 10 ÷ 0 =[ERROR] 計算を終了します。 Traceback (most recent call last): *** 中略 *** ZeroDivisionError: division by zero
1回目の計算機は最後まで正常に処理され、2回目の計算機は、途中でZeroDivisionError
が発生していますが、ちゃんと__exit__()
の処理は実行されていることがわかります。
with
構文を使えば、処理で例外が発生しても、try
except
finally
などを用いずに、後処理を実行してくれます。ファイルをOpenして、その間に例外が発生しても、ちゃんとcloseはされます。
まとめ
以上、今回はPythonのwith
構文についてまとめました。自分で定義したクラスで、前処理、後処理が必要な処理については、__enter__()
と、__exit__()
を実装し、with
構文で呼び出しができるようにすれば、呼び出しが簡単で、品質の高いプログラムにつながると思います。