with構文とは何なのか
Pythonをやっていて、with構文って何だろ?となって、理解できたので整理してみます。細かいところが違うかもしれませんが、動きはつかめるかなと思います。
Python3.3で動作確認しています。
with構文とは
with構文は、ある機能の利用者が、より安全、簡潔にその機能を使えるようにする構文です。既知の定形終了処理であれば、機能作成側でそれをあらかじめ定義し、利用者はwith構文を使うだけで、安全に機能を使うことが出来ます。
with構文を使っていないファイル書き込み例
wfp = open('msg.log', 'w') wfp.write('need call close, if do not use with statement.') wfp.close()
with構文を使ったファイル書き込み例
with open('msg.log', 'w') as wfp: wfp.write('it is example for with statement.') wfp.write('do not need call close().')
with構文を使うと、close()の呼び出しが不要です。withブロックを抜けると、自動でclose()を呼び出してくれます。withブロック内で例外が起きた場合も同様です。ただし、withの行で例外が発生した場合は、close()は呼ばれません。
この仕組みを、実際にwith構文で使えるクラスを作成しながら確認します。
with構文の動き
with構文で使えるクラスには、2つのメソッドが実装されています。
- __enter__(self)
- __exit__(self, exception_type, exception_value, traceback)
この2つを実装したクラスをwithに渡すと次のタイミングで呼ばれます。*1
with # call __enter__() as var_name: Your logic # call __exit__()
呼ばれる順序を、次のクラスを実行して確認して行きます。
Example Python with statement 2013/8/24 fix LABEL comment at main(). LABEL was different to explain on blog. http://d.hatena.ne.jp/reiki4040/20130331/1364723288
※なぜかgist埋め込みがプレビューされないので、リンクで。
まずは、正常に終了する場合。
$ python ExampleWithClass.py --before with statement-- get_instance() __init__ __enter__ do with block. print from ExampleWithClass. __exit__ close() exception_type: None exception_value: None traceback: None --after with statement-- __del__
__enter__, __exit__がwithブロックの開始と終了時に呼ばれていることがわかります。*2
このコードの、printの後に、raiseを入れて例外を起こします。main()内の[LABEL A]の行頭の#を削除して実行します。
$ python ExampleWithClass.py --before with statement-- __init__ __enter__ do with block. __exit__ close() exception_type: <class 'ValueError'> exception_value: traceback: <traceback object at 0x10d21ef80> catch exception --after with statement-- __del__
そうすると例外によって中断され、print from ExampleWithClassは表示されませんが、__exit__()は呼ばれていることがわかります。
また、__exit__()の戻り値によって、例外を握りつぶすかが決まります。Falseを返せば伝搬され、Trueを返すと握りつぶされます。サンプルコードでは、デフォルトでFalseが返されているため、外側のtry-exceptで、例外が補足され、catch exceptionが出力されています。
Trueを返すようにすると、例外が伝搬されないため、exceptに入らず、このメッセージは出力されなくなります。__exit__()内の[LABEL B]2行の行頭の#を削除して実行します。
$ python ExampleWithClass.py --before with statement-- __init__ __enter__ do with block. __exit__ close() exception_type: <class 'ValueError'> exception_value: traceback: <traceback object at 0x10ed00f80> --after with statement-- __del__
例外が握りつぶされたため、catch exceptionが出力されていません。
次に、__enter__()から例外が発生するケース。前述の[LABEL A,B]の行頭に再び#を入れて、__enter__()内の[LABEL C]の行頭の#を削除して実行します。
$ python ExampleWithClass.py --before with statement-- __init__ __enter__ catch exception __del__ --after with statement--
__enter__()から例外が出た場合は、__exit__()は呼ばれません。
with構文用のクラスの作り方
最後に、with構文で使えるクラスを作るための、__enter__()と、__exit__()の詳細です。
__enter__()
__enter__()は、これらのメソッドの実装されたクラスのインスタンスを返します。ここで返されるインスタンスが、with ~ as var_nameのvar_nameに入り、withブロック内で使えます。
__exit__()
__exit__()では、規定の終了処理を行います。また、例外が発生した場合の処理もここに記述します。__exit__()の引数は以下です。*3
exception_type | 例外のインスタンス |
exception_value | 例外のメッセージ???(よくわからず) |
traceback | 例外のトレースバックインスタンス |
正常にwithブロック内の処理が終了すると、この3つの引数には、すべてNoneが入ります。例外発生時は、それぞれに例外に対応した値が渡されます。
I/Oのように、closeといった終了処理が必要な場合は、発生する例外に関係なく、close処理をします。もし、特定の例外に対応した処理が必要な場合は、この引数をもとにifなどで分岐して処理します。
その後、何事も無かったかのように扱うか、ユーザに通知するかを戻り値で決めます。伝搬の要否をbooleanで返します。何も書かなければ、デフォルトでTrueが返ります。
Trueなら、発生した例外がそのまま伝搬され、外側のtry-exceptで処理されます。Falseなら、例外は握りつぶされます。ぱっと状況が思い浮かびませんが、何か対応が決まっており、それを通知しなくてもよい場合(通知されても利用者が対応しようがないもの)は、Falseを返すのかなと思います。
所感
with構文は、コードの品質も上げやすく、コードもすっきりするのでよいですね。ファイル操作にwithを使っていなかったので、今度から使おうと思います。Javaで、finally使ってclose()していないのと同じぐらい、コードとしてはおかしいのかな。
参考
このあたりを参考にさせていただきました。あとは実行確認。
PY習 with文
Understanding Python's "with" statement
実際にライブラリコードで利用している訳ではないので、何か違うところや、いいサンプルがあれば、コメントなどで教えてください。