また 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, in 
    with 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
Trackback

no comment untill now

Add your comment now