Pythonのwith文

Python

このところ興味の矛先が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文が使える状況の時は積極的に使って行きたいです。

コメント

タイトルとURLをコピーしました