En lugar de preguntar cuál es la práctica estándar, ya que a menudo es poco claro y subjetivo, puede intentar consultar el módulo en sí para obtener orientación. En general, usar la with
palabra clave como sugirió otro usuario es una gran idea, pero en esta circunstancia específica puede que no le brinde la funcionalidad que espera.
A partir de la versión 1.2.5 del módulo, MySQLdb.Connection
implementa el protocolo del administrador de contexto con el siguiente código ( 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()
with
Ya hay varias preguntas y respuestas sobre , o puede leer Entender la declaración "con" de Python , pero esencialmente lo que sucede es que se __enter__
ejecuta al comienzo del with
bloque y se __exit__
ejecuta al salir del with
bloque. Puede utilizar la sintaxis opcional with EXPR as VAR
para vincular el objeto devuelto por __enter__
a un nombre si desea hacer referencia a ese objeto más adelante. Entonces, dada la implementación anterior, aquí hay una forma simple de consultar su base de datos:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
La pregunta ahora es, ¿cuáles son los estados de la conexión y el cursor después de salir del with
bloque? El __exit__
método que se muestra arriba solo llama a self.rollback()
o self.commit()
, y ninguno de esos métodos continúa llamando al close()
método. El cursor en sí no tiene un __exit__
método definido, y no importaría si lo hiciera, porque with
solo administra la conexión. Por tanto, tanto la conexión como el cursor permanecen abiertos después de salir del with
bloque. Esto se confirma fácilmente agregando el siguiente código al ejemplo anterior:
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'
Debería ver la salida "cursor abierto; conexión abierta" impresa en stdout.
Creo que debe cerrar el cursor antes de confirmar la conexión.
¿Por qué? La API de MySQL C , que es la base de MySQLdb
, no implementa ningún objeto de cursor, como se implica en la documentación del módulo: "MySQL no admite cursores; sin embargo, los cursores se emulan fácilmente". De hecho, la MySQLdb.cursors.BaseCursor
clase hereda directamente object
y no impone tal restricción a los cursores con respecto al compromiso / retroceso. Un desarrollador de Oracle dijo lo siguiente :
cnx.commit () antes de cur.close () me suena más lógico. Tal vez pueda seguir la regla: "Cierre el cursor si ya no lo necesita". Por lo tanto, commit () antes de cerrar el cursor. Al final, para Connector / Python, no hace mucha diferencia, pero para otras bases de datos podría.
Supongo que eso es lo más cercano a la "práctica estándar" sobre este tema.
¿Existe alguna ventaja significativa en encontrar conjuntos de transacciones que no requieran confirmaciones intermedias para que no tenga que obtener nuevos cursores para cada transacción?
Lo dudo mucho, y al intentar hacerlo, puede introducir un error humano adicional. Mejor decidirse por una convención y ceñirse a ella.
¿Hay muchos gastos generales para obtener nuevos cursores o simplemente no es un gran problema?
La sobrecarga es insignificante y no toca el servidor de la base de datos en absoluto; está completamente dentro de la implementación de MySQLdb. Usted puede mirar en BaseCursor.__init__
github si tiene mucha curiosidad por saber qué está sucediendo cuando crea un nuevo cursor.
Volviendo a cuando estábamos discutiendo with
, tal vez ahora pueda entender por qué la MySQLdb.Connection
clase__enter__
y los __exit__
métodos le brindan un nuevo objeto de cursor en cada with
bloque y no se moleste en seguirlo o cerrarlo al final del bloque. Es bastante ligero y existe únicamente para su conveniencia.
Si es realmente tan importante para usted microgestionar el objeto cursor, puede usar contextlib.closing para compensar el hecho de que el objeto cursor no tiene un __exit__
método definido . De hecho, también puede usarlo para forzar que el objeto de conexión se cierre al salir de un with
bloque. Esto debería mostrar "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'
Tenga en cuenta que with closing(arg_obj)
no llamará a los métodos __enter__
y del objeto argumento __exit__
; será solamente llamar el objeto de discusión close
método al final del with
bloque. (Para ver esto en acción, sólo tiene que definir una clase Foo
con __enter__
, __exit__
y close
métodos que contiene simples print
declaraciones, y comparar lo que sucede cuando usted hacewith Foo(): pass
a lo que sucede cuando lo hace with closing(Foo()): pass
.) Esto tiene dos implicaciones importantes:
Primero, si el modo de confirmación automática está habilitado, MySQLdb realizará BEGIN
una transacción explícita en el servidor cuando utilice su código y pierda la integridad transaccional; no podrá deshacer los cambios, es posible que comience a ver errores de simultaneidad y es posible que no sea inmediatamente obvio por qué.with connection
y confirme o deshaga la transacción al final del bloque. Estos son comportamientos predeterminados de MySQLdb, destinados a protegerlo del comportamiento predeterminado de MySQL de confirmar inmediatamente todas y cada una de las declaraciones DML. MySQLdb asume que cuando usa un administrador de contexto, desea una transacción y usa el explícito BEGIN
para omitir la configuración de confirmación automática en el servidor. Si está acostumbrado a usar with connection
, podría pensar que la confirmación automática está deshabilitada cuando en realidad solo se estaba omitiendo. Puede obtener una sorpresa desagradable si agregaclosing
En segundo lugar, with closing(MySQLdb.connect(user, pass)) as VAR
se une el objeto de conexión para VAR
, en contraste con with MySQLdb.connect(user, pass) as VAR
, que se une un nuevo objeto cursor a VAR
. En el último caso, no tendría acceso directo al objeto de conexión. En su lugar, tendría que utilizar el connection
atributo del cursor , que proporciona acceso de proxy a la conexión original. Cuando el cursor está cerrado, su connection
atributo se establece en None
. Esto da como resultado una conexión abandonada que se mantendrá hasta que ocurra una de las siguientes situaciones:
- Se eliminan todas las referencias al cursor
- El cursor sale fuera de alcance
- La conexión se agota
- La conexión se cierra manualmente mediante las herramientas de administración del servidor.
Puede probar esto monitoreando las conexiones abiertas (en Workbench o usandoSHOW PROCESSLIST
) mientras ejecuta las siguientes líneas una por una:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs