標準的な方法を尋ねる代わりに、それはしばしば不明確で主観的であるため、モジュール自体にガイダンスを求めてみるとよいでしょう。一般に、with
別のユーザーが提案したようにキーワードを使用することは素晴らしいアイデアですが、この特定の状況では、期待する機能が十分に得られない場合があります。
モジュールのバージョン1.2.5以降、次のコード(github)を使用してコンテキストマネージャープロトコルをMySQLdb.Connection
実装します。
def __enter__(self):
if self.get_autocommit():
self.query("BEGIN")
return self.cursor()
def __exit__(self, exc, value, tb):
if exc:
self.rollback()
else:
self.commit()
with
すでにいくつかの既存のQ&Aがあります。または、Pythonの「with」ステートメントについて読むことができますが、基本的に__enter__
は、with
ブロックの開始時に実行され、ブロックを終了すると__exit__
実行されwith
ます。後でそのオブジェクトを参照する場合は、オプションの構文with EXPR as VAR
を使用して、によって返されたオブジェクトを__enter__
名前にバインド できます。したがって、上記の実装を前提として、データベースにクエリを実行する簡単な方法は次のとおりです。
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
問題は、with
ブロックを終了した後の接続とカーソルの状態はどうなっているのかということです。上記の__exit__
メソッドはself.rollback()
またはのみself.commit()
を呼び出し、これらのメソッドはどちらもメソッドを呼び出しませんclose()
。カーソル自体には__exit__
メソッドが定義されていませんwith
。接続を管理しているだけなので、メソッドが定義されていても問題ありません。したがって、with
ブロックを終了した後も、接続とカーソルの両方が開いたままになります。これは、上記の例に次のコードを追加することで簡単に確認できます。
try:
cursor.execute('select 1;')
print 'cursor is open;',
except MySQLdb.ProgrammingError:
print 'cursor is closed;',
if connection.open:
print 'connection is open'
else:
print 'connection is closed'
stdoutに「カーソルが開いています。接続が開いています」という出力が出力されます。
接続を確定する前にカーソルを閉じる必要があると思います。
どうして?の基礎となるMySQLC APIMySQLdb
は、モジュールのドキュメントに示されているように、カーソルオブジェクトを実装していません。「MySQLはカーソルをサポートしていませんが、カーソルは簡単にエミュレートされます。」実際、MySQLdb.cursors.BaseCursor
クラスはobject
コミット/ロールバックに関してカーソルから直接継承し、そのような制限を課しません。Oracle開発者はこう言っています:
cur.close()の前のcnx.commit()は、私にとって最も論理的に聞こえます。たぶん、「もう必要ない場合はカーソルを閉じる」というルールに従うことができます。したがって、カーソルを閉じる前にcommit()を実行します。結局、Connector / Pythonの場合、大きな違いはありませんが、他のデータベースでは大きな違いがあります。
これは、このテーマに関する「標準的な慣行」に到達するのと同じくらい近いと思います。
トランザクションごとに新しいカーソルを取得する必要がないように、中間コミットを必要としないトランザクションのセットを見つけることに大きな利点はありますか?
私はそれを非常に疑っています、そしてそうしようとすると、あなたは追加のヒューマンエラーを導入するかもしれません。コンベンションを決定し、それに固執する方が良いです。
新しいカーソルを取得するためのオーバーヘッドはたくさんありますか、それとも大したことではありませんか?
オーバーヘッドはごくわずかであり、データベースサーバーにはまったく影響しません。それは完全にMySQLdbの実装内にあります。あなたは、することができます見てBaseCursor.__init__
githubの上のあなたは、あなたが新しいカーソルを作成するときに何が起こっているかを知って、本当に興味があれば。
以前に説明したときに戻るとwith
、MySQLdb.Connection
クラス__enter__
と__exit__
メソッドがすべてのwith
ブロックでまったく新しいカーソルオブジェクトを提供し、それを追跡したり、ブロックの最後で閉じたりする必要がない理由を理解できたと思います。それはかなり軽量で、純粋にあなたの便宜のために存在します。
カーソルオブジェクトを細かく管理することが本当に重要な場合は、contextlib.closeingを使用して、カーソルオブジェクトに__exit__
メソッドが定義されていないことを補うことができます。さらに言えば、これを使用して、with
ブロックを終了するときに接続オブジェクトを強制的に閉じることもできます。これにより、「my_cursは閉じています。my_connは閉じています」と出力されます。
from contextlib import closing
import MySQLdb
with closing(MySQLdb.connect(...)) as my_conn:
with closing(my_conn.cursor()) as my_curs:
my_curs.execute('select 1;')
result = my_curs.fetchall()
try:
my_curs.execute('select 1;')
print 'my_curs is open;',
except MySQLdb.ProgrammingError:
print 'my_curs is closed;',
if my_conn.open:
print 'my_conn is open'
else:
print 'my_conn is closed'
with closing(arg_obj)
引数オブジェクト__enter__
と__exit__
メソッドを呼び出さないことに注意してください。引数オブジェクトのメソッドは、最後に呼び出されるだけclose
です。with
ブロック。(単にクラスを定義し、この動作を確認するにはFoo
で__enter__
、__exit__
、およびclose
方法は、単純含むprint
文を、そしてあなたが行うときに何が起こるかを比較するwith Foo(): pass
あなたはときに何が起こるかにwith closing(Foo()): pass
。)これは、二つの重要な意味を持っています:
まず、自動コミットモードが有効になっBEGIN
ている場合with connection
、ブロックの最後でトランザクションを使用してコミットまたはロールバックすると、MySQLdbはサーバー上で明示的なトランザクションを実行します。これらはMySQLdbのデフォルトの動作であり、すべてのDMLステートメントを即座にコミットするというMySQLのデフォルトの動作からユーザーを保護することを目的としています。MySQLdbは、コンテキストマネージャーを使用するときにトランザクションが必要であると想定し、明示BEGIN
を使用してサーバーの自動コミット設定をバイパスします。コードの使用に慣れていて、トランザクションの整合性が失われている場合。変更をロールバックできなくなり、同時実行のバグが発生する可能性があり、その理由がすぐにはわからない場合があります。with connection
は、実際にはバイパスされているだけのときに自動コミットが無効になっていると思うかもしれません。追加すると不快な驚きを感じるかもしれませんclosing
第二に、with closing(MySQLdb.connect(user, pass)) as VAR
結合した接続オブジェクトとVAR
は対照的に、with MySQLdb.connect(user, pass) as VAR
結合し、新しいカーソルオブジェクトへのVAR
。後者の場合、接続オブジェクトに直接アクセスすることはできません。代わりに、カーソルを使用する必要がありますconnection
に、元の接続へのプロキシアクセスを提供する属性。カーソルを閉じると、そのconnection
属性はに設定されNone
ます。これにより、接続が放棄され、次のいずれかが発生するまで接続が維持されます。
- カーソルへのすべての参照が削除されます
- カーソルがスコープ外になります
- 接続がタイムアウトします
- サーバー管理ツールを使用して、接続を手動で閉じます
これをテストするには、次の行を1つずつ実行しながら、開いている接続を(Workbenchで、またはを使用してSHOW PROCESSLIST
)監視します。
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs