また Python+MySQL ネタ。
カーソルを使ったら閉じないといけません。処理後にその流れでそのまま cursor.close() を呼ぶように書くと例外が飛んだり長い処理の途中でうっかり return したりしたときに呼び漏れが発生するので、try~finally のような構造を使いたいところです。(Java でもたぶんそう(20年前の知識)。C++ だとデストラクタが使えるので違う書き方になる)
cursor = conn.cursor() try: cursor.execute("SELECT * FROM table") rows = cursor.fetchall() ... finally: cursor.close()
ところで、Python には with 文というのがあって、 with 文の中のブロックを(抜け方にかかわらず)抜けたときに一定の終了処理を自動で実行することができます。PyMySQL と mysqlclient ではカーソルに対してこれを使うことができます。
with conn.cursor() as cursor: cursor.execute("SELECT * FROM table") rows = cursor.fetchall() ... # with から抜けるときに cursor.close() が自動的に呼ばれる
残念なことに MySQL Connector/Python だとこの書式は使えません。with文は with の後に来るオブジェクトの __enter__ を呼んで with を抜けるときに __exit__ を呼ぶという仕組みなのですが、カーソルにはこれらのマジックメソッドが実装されていないからです。
Traceback (most recent call last): File "./test_cursor_with.py", line 29, inwith conn.cursor() as cur: AttributeError: __enter__
ただし、 with を抜けるときに close() を呼んでくれるようになる closing というラッパーメソッドが Python 標準の contextlib モジュールの中にあるので、自前でラッパーを書いたりする必要はありません。
from contextlib import closing with closing(conn.cursor()) as cursor: cursor.execute("SELECT * FROM table") rows = cursor.fetchall() ...
さて、カーソルを with で使えるならコネクションでも使ってみようかと思うわけですが、何も考えずに with に渡すとおかしな挙動になります。(commit や rollback についてはとりあえず考えないことにします)
with pymysql.connect(**mysql_kwargs) as conn: print(conn.__class__.__module__ + "." + conn.__class__.__name__)
pymysql.cursors.Cursor
コネクションが来ることを期待していたのですが、何故かカーソルになって渡ってきます。なんでやねん。
コネクション型である pymysql.connections.Connection の定義を見てもそもそも __enter__ とかは定義されていないのでエラーになるのが期待されると思うのですが、なんでこんな挙動になるのか分かりません。うーん。
使用環境は以下の通り
- Ubuntu 18.04 LTS
- Python 3.6.8
- PyMySQL 0.8.0
- MySQL Connector/Python 2.1.6
- mysqlclient 1.3.10
no comment untill now