PythonでWith構文を利用すると、呼び出し側で明示的にOpen(初期処理),Close(終了処理)を呼び出さなくても自動的に実行されます。
よくあるファイルやデータベースへのアクセスで終了処理を呼び忘れてClose漏れなどを効果的に防ぐことができます。
PythonでWith構文に対応したクラスを作成するには「__enter__()」と「__exit__()」メソッドを実装します。
PythonのWith構文の使い方
PythonでWith構文を使うと利用者に初期処理と終了処理を意識させることなく、クラス側で安全に初期処理と終了処理を確実に実行するようにできるようになります。
With構文のブロックを開始したタイミングで初期処理が、With構文のブロックを抜けたタイミングで終了処理が自動的に実行されます。
With構文__enter__と__exit__の実装方法
では実際にPythonのWith構文で必要な__enter__と__exit__メソッドを実装してみます。
今回はsqlite3のデータベースに対してのアクセスするクラスをWith構文で実装します。
import sys import sqlite3 class WithTestClass( object ): #初期処理にあたる__enter__メソッド def __enter__( self ) : print( self.__class__.__name__ + ".__enter__():Start" ) #開始処理 try : self.db = sqlite3.connect( ":memory:" ) self.db.execute( "CREATE TABLE test_table ( name TEXT, age INTEGER )" ) self.db.execute( "INSERT INTO test_table VALUES ( '山田太郎', 16 )" ) self.db.execute( "INSERT INTO test_table VALUES ( '岩鬼正美', 16 )" ) self.db.execute( "INSERT INTO test_table VALUES ( '殿馬一人', 16 )" ) self.db.execute( "INSERT INTO test_table VALUES ( '土井垣将', 18 )" ) except Exception as e: print( self.__class__.__name__ + ".__enter__():" + str( e ) ) self.db = None print( self.__class__.__name__ + ".__enter__():End" ) return self #終了処理にあたる__exit__メソッド def __exit__( self, exc_type, exc_val, exc_tb): print( self.__class__.__name__ + ".__exit__():Start" ) #終了処理 if self.db != None : self.db.close() print( self.__class__.__name__ + ".__exit__():End" ) #データベースに対してSQLを実行する def execute( self, sql ) : return self.db.execute( sql )
ポイントは__enter__メソッドでデータベースとのコネクションを開始し、__exit__メソッドでデータベースとのコネクションを終了しています。
このクラスを利用する場合は
with WithTestClass() as db: try : result = db.execute( "SELECT * FROM test_table" ) print( "SELECTした結果は" + str(result.fetchall()) ) except Exception as e: print( "例外キャッチ" ) print( str( e )) raise
のように「with」で利用するクラス名を指定し、そのインスタンス名を「as」で指定します。
上記のソースを実行すると
WithTestClass.__enter__():Start WithTestClass.__enter__():End SELECTした結果は[('山田太郎', 16), ('岩鬼正美', 16), ('殿馬一人', 16), ('土井垣将', 18)] WithTestClass.__exit__():Start WithTestClass.__exit__():End
となり、「__enter__ → withブロック内 → __exit__」と処理が実行されいることがわかります。
With構文で例外が発生した場合
万が一With構文で例外が発生した場合でも、きちんと__exit__メソッド(終了処理)が呼び出されます。
以下では「SELECT」でワザとエラーが発生するようにしてあります。
with WithTestClass() as db: try : result = db.execute( "SELXXXXECT * FROM test_table" ) print( "SELECTした結果は" + str(result.fetchall()) ) except Exception as e: print( "例外キャッチ" ) print( str( e )) raise
上記のソースを実行すると
WithTestClass.__enter__():Start WithTestClass.__enter__():End 例外キャッチ near "SELXXXXECT": syntax error WithTestClass.__exit__():Start WithTestClass.__exit__():End Traceback (most recent call last): File "with_test.py", line 40, in <module> result = db.execute( "SELXXXXECT * FROM test_table" ) File "with_test.py", line 36, in execute return self.db.execute( sql ) sqlite3.OperationalError: near "SELXXXXECT": syntax error
となり、途中で例外が発生しても__exit__メソッド(終了処理)がコールされています。
まとめ
With構文を利用すれば、呼び出し側にOpen(初期処理)やClose(終了処理)を意識させることなく、安全に実行することができるようになります。
以上、With構文の使い方ーOpen(初期処理),Close(終了処理)の作り方でした。