Daripada menanyakan apa itu praktik standar, karena sering kali tidak jelas dan subjektif, Anda dapat mencoba melihat modul itu sendiri untuk panduan. Secara umum, menggunakan with
kata kunci seperti yang disarankan pengguna lain adalah ide bagus, tetapi dalam situasi khusus ini mungkin tidak memberikan fungsionalitas yang Anda harapkan.
Pada modul versi 1.2.5, MySQLdb.Connection
mengimplementasikan protokol pengelola konteks
dengan kode berikut (github
):
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()
Ada beberapa T&J yang ada tentang with
sudah, atau Anda dapat membaca Memahami pernyataan "dengan" Python
, tetapi pada dasarnya yang terjadi adalah __enter__
dijalankan di awal with
blokir, dan __exit__
dijalankan setelah meninggalkan with
memblokir. Anda dapat menggunakan sintaks opsional with EXPR as VAR
untuk mengikat objek yang dikembalikan oleh __enter__
ke nama jika Anda berniat untuk merujuk objek itu nanti. Jadi, dengan penerapan di atas, berikut adalah cara sederhana untuk mengkueri database Anda:
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,),)"
Pertanyaannya sekarang adalah, bagaimana status koneksi dan kursor setelah keluar dari with
memblokir? __exit__
metode yang ditampilkan di atas hanya memanggil self.rollback()
atau self.commit()
, dan tidak satu pun dari metode tersebut yang memanggil close()
metode. Kursor itu sendiri tidak memiliki __exit__
metode yang ditentukan – dan tidak masalah jika itu berhasil, karena with
hanya mengelola koneksi. Oleh karena itu, koneksi dan kursor tetap terbuka setelah keluar dari with
memblokir. Ini mudah dikonfirmasi dengan menambahkan kode berikut ke contoh di atas:
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'
Anda akan melihat output "kursor terbuka; koneksi terbuka" dicetak ke stdout.
Saya yakin Anda perlu menutup kursor sebelum melakukan koneksi.
Mengapa? MySQL C API
, yang merupakan dasar untuk MySQLdb
, tidak mengimplementasikan objek kursor apa pun, seperti yang tersirat dalam dokumentasi modul:"MySQL tidak mendukung kursor; namun, kursor mudah ditiru."
Memang, MySQLdb.cursors.BaseCursor
kelas mewarisi langsung dari object
dan tidak memberlakukan batasan seperti itu pada kursor sehubungan dengan komit/kembalikan. Pengembang Oracle mengatakan ini
:
cnx.commit() sebelum cur.close() terdengar paling logis bagi saya. Mungkin Anda bisa mengikuti aturan:"Tutup kursor jika Anda tidak membutuhkannya lagi." Jadi komit() sebelum menutup kursor. Pada akhirnya, untuk Connector/Python, itu tidak membuat banyak perbedaan, tetapi atau database lain mungkin.
Saya berharap itu sedekat Anda akan mendapatkan "praktik standar" tentang hal ini.
Apakah ada keuntungan signifikan untuk menemukan kumpulan transaksi yang tidak memerlukan komitmen perantara sehingga Anda tidak perlu mendapatkan kursor baru untuk setiap transaksi?
Saya sangat meragukannya, dan dalam mencoba melakukannya, Anda mungkin menambahkan kesalahan manusia. Lebih baik memutuskan konvensi dan menaatinya.
Apakah ada banyak biaya tambahan untuk mendapatkan kursor baru, atau ini bukan masalah besar?
Overhead dapat diabaikan, dan tidak menyentuh server database sama sekali; itu sepenuhnya dalam implementasi MySQLdb. Anda dapat melihat BaseCursor.__init__
di github
jika Anda benar-benar ingin tahu apa yang terjadi saat Anda membuat kursor baru.
Kembali ke awal ketika kita mendiskusikan with
, mungkin sekarang Anda dapat memahami mengapa MySQLdb.Connection
kelas __enter__
dan __exit__
metode memberi Anda objek kursor baru di setiap with
blokir dan jangan repot-repot melacaknya atau menutupnya di ujung blok. Ini cukup ringan dan ada murni untuk kenyamanan Anda.
Jika benar-benar penting bagi Anda untuk mengelola objek kursor secara mikro, Anda dapat menggunakan contextlib.closing
untuk menebus fakta bahwa objek kursor tidak memiliki __exit__
yang ditentukan metode. Dalam hal ini, Anda juga dapat menggunakannya untuk memaksa objek koneksi menutup sendiri setelah keluar dari with
memblokir. Ini akan menampilkan "my_curs ditutup; my_conn ditutup":
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'
Perhatikan bahwa with closing(arg_obj)
tidak akan memanggil __enter__
objek argumen dan __exit__
metode; itu akan hanya panggil close
objek argumen metode di akhir with
memblokir. (Untuk melihat ini beraksi, cukup tentukan kelas Foo
dengan __enter__
, __exit__
, dan close
metode yang berisi print
simple sederhana pernyataan, dan bandingkan apa yang terjadi ketika Anda melakukan with Foo(): pass
apa yang terjadi ketika Anda melakukan with closing(Foo()): pass
.) Ini memiliki dua implikasi signifikan:
Pertama, jika mode komit otomatis diaktifkan, MySQLdb akan BEGIN
transaksi eksplisit di server saat Anda menggunakan with connection
dan komit atau kembalikan transaksi di akhir blok. Ini adalah perilaku default MySQLdb, yang dimaksudkan untuk melindungi Anda dari perilaku default MySQL yang segera melakukan setiap dan semua pernyataan DML. MySQLdb mengasumsikan bahwa ketika Anda menggunakan manajer konteks, Anda menginginkan transaksi, dan menggunakan BEGIN
eksplisit untuk melewati pengaturan autocommit di server. Jika Anda terbiasa menggunakan with connection
, Anda mungkin mengira komit otomatis dinonaktifkan padahal sebenarnya itu hanya dilewati. Anda mungkin mendapatkan kejutan yang tidak menyenangkan jika Anda menambahkan closing
ke kode Anda dan kehilangan integritas transaksional; Anda tidak akan dapat mengembalikan perubahan, Anda mungkin mulai melihat bug konkurensi dan mungkin tidak segera jelas alasannya.
Kedua, with closing(MySQLdb.connect(user, pass)) as VAR
mengikat objek koneksi ke VAR
, berbeda dengan with MySQLdb.connect(user, pass) as VAR
, yang mengikat objek kursor baru ke VAR
. Dalam kasus terakhir, Anda tidak akan memiliki akses langsung ke objek koneksi! Sebagai gantinya, Anda harus menggunakan connection
kursor atribut, yang menyediakan akses proxy ke koneksi asli. Saat kursor ditutup, connection
its atribut disetel ke None
. Ini menghasilkan koneksi yang ditinggalkan yang akan bertahan sampai salah satu hal berikut terjadi:
- Semua referensi kursor dihapus
- kursor keluar dari ruang lingkup
- Waktu koneksi habis
- Koneksi ditutup secara manual melalui alat administrasi server
Anda dapat mengujinya dengan memantau koneksi terbuka (di Workbench atau dengan menggunakan SHOW PROCESSLIST
) saat menjalankan baris berikut satu per satu:
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