With文の基本動作
With文の動作を次の形式のwith文で確認してみた。
with オブジェクト生成式 as target: スィート
オブジェクト生成式において用いられるクラス型は、2つのメソッド__enter__(self)と__exit__(self, p0, p1, p2)が宣言されていなければならない。
メソッド__enter__(self)は、with文のスィート(すなわちブロック)の実行前に呼び出される。その戻り値はtargetに設定される。
メソッド__exit__(self, p0, p1, p2)は、with文のスィートの実行後に呼び出される。パラメータp0, p1, p2は、スィート内で例外が生成されたときの情報が設定され、それらの値は__exit__内で例外処理を行うときに用いられる。ここでは、例外処理は行わないので無視する。
With文用の簡単なクラス型を以下のように用意してみた。
class C0:
def
__init__(self):
pass
def
__enter__(self):
print('Ready!')
return 'Hello!'
def
__exit__(self, p0, p1, p2):
print('Good-bye!')
関数__enter__の実行時に、「Ready!」が出力され、文字列「’Hello!’」が関数値として返される。この値がwith文のtargetに設定される。
関数__exit__は、「'Good-bye!’」の出力のみを行う。
上のクラス型C0を用いるwith文を次のように書いてみた。
with C0() as c:
print(c)
このコードを実行すると以下の出力が得られる。
Ready!
Hello!
Good-bye!
関数__enter__が実行されたときの出力「Ready!」、target cのprint(c)による出力「Hello!」、関数__exit__による出力「Good-bye!」が順番に出力されている。
上のサンプルプログラムを1つのスクリプトにまとめたものを、リスト1に示す。
リスト1
class C0:
def
__init__(self):
pass
def
__enter__(self):
print('Ready!')
return 'Hello!'
def
__exit__(self, p0, p1, p2):
print('Good-bye!')
with C0() as c:
print(c)
with文は、スィートの実行前の前処理と、実行後の後処理を自動で行うために用いられるが、必要な後処理を関数__exit__に書いておかなければならない。書かれていない処理は行われない。
例えば、次のようにファイルを開くクラス型Open0を用意したとする。
class Open0:
def
__init__(self, filename):
self.filename = filename
def
__enter__(self):
print('Ready!')
self.f = open(self.filename, 'w')
return self.f
def
__exit__(self, p0, p1, p2):
print('Good-bye!')
オブジェクト生成時に、関数__init__によってファイル名を設定している。そのファイル名による書き込み用ファイルオブジェクトを、関数__enter__の次のコードよってwith文のtargetに返している。
self.f = open(self.filename,
'w')
return self.f
with文の終了時に実行される関数__exit__では、print文が実行されているだけで、ファイルの操作は行われていない。
このクラス型Open0を以下のように用いてみる。
with
Open0('check.txt') as cf:
cf.write('check_1\n')
cf.write('check_2')
cf.close()
with文が終了しても、ファイルを閉じる操作がwith文の終了時に行われていないので、次のコード
cf.write('check_2')
cf.close()
がエラーなく実行される。
実行終了後に、出力ファイルを開くと以下のようになっている。
check_1
check_2
with文の後で出力された文字列「check_2」も問題なく出力されている。
上のサンプルコードを1つのスクリプトにまとめたものを、リスト2に示す。
リスト2
class Open0:
def
__init__(self, filename):
self.filename = filename
def
__enter__(self):
print('Ready!')
self.f = open(self.filename, 'w')
return self.f
def
__exit__(self, p0, p1, p2):
print('Good-bye!')
with Open0('check.txt') as cf:
cf.write('check_1\n')
cf.write('check_2')
cf.close()
上のクラス型Open0では、関数__exit__においてファイルの後処理が行われていない。ファイルの後処理を関数__exit__で行うクラス型Open1を以下のように用意した。
class Open1:
def
__init__(self, filename):
self.filename = filename
def
__enter__(self):
print('Ready!')
self.f = open(self.filename, 'w')
return self.f
def
__exit__(self, p0, p1, p2):
self.f.close()
print('Good-bye!')
上のクラス型Open1では、with文終了時に実行される関数__exit__においてファイルが閉じられている。
このクラス型Open1を以下のようにwith文で用いてみる。
with Open1('check.txt') as cf:
cf.write('check_1\n')
cf.write('check_2')
cf.close()
実行すると、with文の終了後のファイル出力において、次のエラーメッセージが表示される。
cf.write('check_2')
ValueError: I/O operation on closed
file.
With文の終了時に、ファイルが閉じられていることが分かる。
出力ファイルを開くと以下のようになっている。
check_1
with文実行時の出力が、正しく保存されていることが分かる。
上のコード例を1つのスクリプトにまとめたものをリスト3に示す。
リスト3
class Open1:
def
__init__(self, filename):
self.filename = filename
def
__enter__(self):
print('Ready!')
self.f = open(self.filename, 'w')
return self.f
def
__exit__(self, p0, p1, p2):
self.f.close()
print('Good-bye!')
with Open1('check.txt') as cf:
cf.write('check_1\n')
cf.write('check_2')
cf.close()
なお、Pythonデータ分析の解説書として<岡本安晴「いまさら聞けないPythonでデータ分析」丸善出版>を上梓した。