Up

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 cprint(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データ分析丸善出版>を上梓した。

 

 

Up