PythonでSQLiteを使う時にクローズ処理は行うべき?
python bottle+sqlite3で複数ユーザの更新を受け付ける方法 の質問をした時に、下記のコメントをいただきました。
answer 関数や init_table 関数で with sqlite3.connect(db_name) as ... としていますけれども、ここは context manager を使って(from contextlib import closing)、with closing(sqlite3.connect(db_name)) as ... とすべきではないでしょうか
Python 3.xからSQLite3を呼び出した後にクローズ処理は必ず行うべきものでしょうか。
そしてクローズ処理を行う場合、どのコードが推奨されるでしょうか。
- contextlib.closing
with closing(sqlite3.connect('hoge.db')) as c:
- try-finally
finally:
c.close()
- withのみ(クローズ無し?)
with sqlite3.connect('hoge.db') as c:
無学ゆえにコメントをいただくまでcontextlib.closing
の存在を知りませんでした。
日本語でsqlite3のサンプルコードを見ても、closingを使わずにwithのみが書かれているものや、finally
ブロックを使わずに正常処理としてclose()
を行うもの、close()
しないものが多く見つかります。
本家SOの類似質問への回答では、下記について言及するとともにcontextlib.closing
の使用を推奨しています。
However, the
connection.__exit__()
method doesn't close the connection, it commits the transaction on a successful completion, or aborts it when there is an exception.
意訳:
(flaskチュートリアルのconnect_db()
はconnection manager
のようにsqlite3のconnectionを返す。)だかしかし、connection.__exit__()
関数は接続を切断しない。これはブロックの処理を正常に実行したらコミットし、例外が発生したら破棄する。
本家SOの別質問では、「クローズした方が良いんじゃない?」程度に軽く推奨しているように読み取れます。
クローズすることで利用者にはどのようなメリットがあるのでしょうか。
なお、withを使いclosingを使わない下記のスクリプトを並列処理しても、ロックはかからずに正常終了しました。
下記は複数ユーザが個別のプログラムで1つのデータベースファイルに同時アクセスする状況を再現する状況を、並列処理で強引に再現しようとしています。
import sqlite3
from contextlib import closing
from multiprocessing import Pool
#DBがなければ作る
def get_db():
p = 'quiz.db'
conn = sqlite3.connect(p)
init_answer(conn)
return conn
def init_answer(conn):
if has_table(conn, 'answer'):
return
conn.execute(u"""
create table answer (
auid varchar(50) primary key,
avalue integer
);
"""
)
def has_table(conn, name):
q = conn.execute("select count(*) from sqlite_master where type='table' and name='{0}'".format(name))
c = q.fetchone()[0]
return c != 0
def replace_row(i):
# closingの有無にかかわらず、同時実行に正常終了する
with sqlite3.connect('quiz.db') as conn:
#with closing(sqlite3.connect('quiz.db')) as conn:
conn.execute("replace into answer values(?, ?)", ('fuga', i))
if __name__ == '__main__':
conn = get_db()
conn.close()
#同時アクセスのために効率の悪い並列処理を行っています。このコードを再利用する際にはベストアンサーもご参照願います。
p = Pool(4)
p.map(replace_row, list(range(100)))
p.close()