多くの場合、それは不明確で主観的であるため、標準的な方法を尋ねる代わりに、モジュール自体にガイダンスを求めてみてください。一般に、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()
すでにいくつかの既存の Q&A があります。または、 Python の "with" ステートメントを理解with
するを読むこともできますが、本質的に何が起こるかは、ブロックの開始時に実行され、ブロックを離れるときに実行されます。後でそのオブジェクトを参照する場合は、オプションの構文を使用して、返されたオブジェクトを名前にバインド できます。したがって、上記の実装を考えると、データベースにクエリを実行する簡単な方法は次のとおりです。__enter__
with
__exit__
with
with EXPR as VAR
__enter__
connection = MySQLdb.connect(...)
with connection as cursor: # connection.__enter__ executes at this line
cursor.execute('select 1;')
result = cursor.fetchall() # connection.__exit__ executes after this line
print result # prints "((1L,),)"
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'
「cursor is open; connection is open」という出力が stdout に出力されるはずです。
接続をコミットする前にカーソルを閉じる必要があると思います。
なんで?の基礎であるMySQL C APIMySQLdb
は、モジュールのドキュメントに示されているように、カーソル オブジェクトを実装していません。「MySQL はカーソルをサポートしていません。ただし、カーソルは簡単にエミュレートできます。実際、MySQLdb.cursors.BaseCursor
クラスは直接継承しobject
、コミット/ロールバックに関してカーソルにそのような制限を課しません。Oracle の開発者は次のように述べています。
cur.close() の前の cnx.commit() は、私にとって最も論理的に聞こえます。おそらく、「もう必要ない場合はカーソルを閉じてください」というルールに従うことができます。したがって、カーソルを閉じる前に commit() を実行します。最終的に、Connector/Python の場合、大きな違いはありませんが、他のデータベースではそうなる可能性があります。
この件に関する「標準的な実践」に近いものになると思います。
トランザクションごとに新しいカーソルを取得する必要がないように、中間コミットを必要としない一連のトランザクションを見つけることに大きな利点はありますか?
私はそれを非常に疑っています.そうしようとすると、追加の人的エラーが発生する可能性があります. 規約を決めて、それを守る方がよいでしょう。
新しいカーソルを取得するために多くのオーバーヘッドがありますか?それとも大したことではないのでしょうか?
オーバーヘッドはごくわずかで、データベース サーバーにはまったく影響しません。それは完全に MySQLdb の実装内にあります。新しいカーソルを作成したときに何が起こっているのか知りたい場合は、githubを参照してください。BaseCursor.__init__
以前の の説明に戻ると、クラスとメソッドがすべてのブロックでまったく新しいカーソル オブジェクトを提供し、それを追跡したり、ブロックの最後で閉じたりするwith
必要がない理由を理解できるでしょう。それはかなり軽量で、純粋にあなたの便宜のために存在します。MySQLdb.Connection
__enter__
__exit__
with
カーソル オブジェクトを細かく管理することが本当に重要な場合は、contextlib.closingを使用して、カーソル オブジェクトにメソッドが定義されていないという事実を補うことができます__exit__
。with
さらに言えば、それを使用して、ブロックを終了するときに接続オブジェクトを強制的に閉じることもできます。これにより、「my_curs is closed; my_conn is closed」が出力されます。
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__
メソッドを呼び出さないことに注意してください。ブロックの最後で引数オブジェクトのメソッドを呼び出すだけです。(これを実際に見るには、単純なステートメントを含む、、およびメソッドでクラスを定義し、 を実行したときに何が起こるかを比較します。) これには 2 つの重要な意味があります。close
with
Foo
__enter__
__exit__
close
print
with Foo(): pass
with closing(Foo()): pass
まず、自動コミット モードが有効になっている場合、ブロックの最後でトランザクションを使用してコミットまたはロールバックすると、MySQLdb はBEGIN
サーバー上で明示的なトランザクションを実行します。with connection
これらは 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 # None
my_curs.connection.close() # throws AttributeError, but connection still open
del my_curs # connection will close here