このところ興味の矛先がPythonからRaspberry Piに移りつつあります…w
なので、最近はあんまり言語リファレンスを読んでないんですが、今まで読んだ中でかなり便利そうな文があったので忘れないうちにまとめておきます。
Pythonのwith文
それはwith文です。
ちょっと前に読んだリーダブルコードでも紹介されてたんですが、with文を使うとクリーンアップが必要なコードをシンプルに記述出来ます。
例えばファイルを操作する場合、よく
try: f = open('foo.txt', 'w') print 'file opened.' finally: f.close() print 'file closed.' #実行結果 file opened. file closed.
こんな感じでtry〜finally文を使います。
これと同じことをwith文を使うと
with open('foo.txt', 'w') as f: print 'file opened.' #実行結果 file opened. #closeされてる? >>> f >>> <closed file 'foo.txt', mode 'w' at 0x10bebc810>
こんな感じで、わざわざclose()メソッドを呼び出さなくてもきちんとcloseされるのでスッキリと記述出来ます。
with文が使えるオブジェクト
言語リファレンスによると
with 文は、 __enter__() メソッドがエラーなく終了した場合には __exit__() が常に呼ばれることを保証します。
ということで、見方を変えるとwith文が使えるオブジェクトでは少なくとも__enter__()メソッドと__exit()__メソッドの両方が定義されている必要がありそうです。
(ただ、__enter__()メソッドと__exit__()メソッドが定義されていれば必ず使えるわけではありません。)
定義されているメソッドを調べる方法
__enter__()メソッドと __exit__()メソッドが定義されてるか調べるには組込み関数dir()が使えそうです。
with_able = set(['__enter__','__exit__']) f = open('foo.txt', 'w') builtin = set(dir(f)) f.close() if len(withable - builtin) == 0: print 'can use with statement.' else: print "can't use with statement." #実行結果 can use with statement.
with文が使えるクラスを自作してみる
__enter__()メソッドと__exit__()メソッドの両方を定義すれば良いわけですが、with文を使えるようにするにはちょっとだけポイントがあります。
__enter__()メソッドのポイント
with文のas節で指定された変数に__enter__()メソッドの戻り値が代入されるためreturnが必要(通常はselfを返しておけば良いのかな?)。
__exit__()メソッド
self以外に引数を3つ取ります。
この3つの引数にはwith文のコードブロック中で例外が発生した時に値が代入され、正常終了した場合は全てNoneになります。
あと、with文のコードブロックで例外が発生した場合でも__exit__()メソッドの戻り値をTrueにしておくと例外はココで止まりwith文以降も処理されます。
__exit__()メソッドの戻り値を指定しない場合やFalseに設定した場合は例外がwith文の外に送出されるので処理はココで止まります。
言語リファレンスによると__exit__()は例外を再送出するべきじゃないとのことなのでTrueにしておくのが良さそうです。
with文が使えるクラスのサンプル
試しにwith文が使える割り算のクラスを作ってみます。
class DevideInt: def __init__(self): print '__init__' def __enter__(self): print '__enter__' return self def calc(self, dividend , divisor): quotient = dividend / divisor print '{0} / {1} = {2}'.format(dividend, divisor, quotient) def __exit__(self, exc_type, exc_value, traceback): print '__exit__' print 'exc_type : ', exc_type print 'exc_value: ', exc_value print 'traceback: ', traceback return True
こんな感じにして、わざと例外が出るようなコードを実行してみます。
>>> for i in range(3): ... with DevideInt() as d: ... d.calc(i + 1, i) #実行結果 __init__ __enter__ __exit__ exc_type : <type 'exceptions.ZeroDivisionError'> exc_value: integer division or modulo by zero traceback: <traceback object at 0x106fbf1b8> __init__ __enter__ 2 / 1 = 2 __exit__ exc_type : None exc_value: None traceback: None __init__ __enter__ 3 / 2 = 1 __exit__ exc_type : None exc_value: None traceback: None
最初で例外が発生してますが処理が継続出来てますね(計算結果がおかしいのはスルーしてくださいw)。
__exit__()メソッドに戻り値を指定しなかったりFalseにした場合の実行結果はこうなります。
>>> for i in range(3): ... with DevideInt() as d: ... d.calc(i+1, i) ... #実行結果 __init__ __enter__ __exit__ exc_type : <type 'exceptions.ZeroDivisionError'> exc_value: integer division or modulo by zero traceback: <traceback object at 0x10e96d1b8> Traceback (most recent call last): File "<stdin>", line 3, in <module> File "<stdin>", line 8, in calc ZeroDivisionError: integer division or modulo by zero
例外がwith文の外に送出されるので処理が止まってます。
with文のネスト
今のところ使う機会がないんですが、with文はネストも出来るみたいです。
with A() as a, B() as b:
は
with A() as a: with B() as b:
と同じとのこと。
ネストが深くならないのは良いですね。使いどころがあればですが…w
伝統的なtry〜finally文も悪く無いですがコードが見やすくなるwith文が使える状況の時は積極的に使って行きたいです。
コメント