python - and - ¿Por qué el administrador de contexto de conexión MySQLdb no cierra el cursor?
pymysql tutorial (1)
MySQLdb Connections
tiene un administrador de contexto rudimentario que crea un cursor al ingresar , retrocede o se confirma al salir , y no suprime implícitamente las excepciones. Desde la fuente de conexión :
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()
Entonces, ¿alguien sabe por qué el cursor no está cerrado al salir?
Al principio, asumí que era porque cerrar el cursor no hacía nada y que los cursores solo tenían un método cercano en deferencia a la API de la base de datos de Python (consulte los comentarios a esta respuesta ). Sin embargo, el hecho es que cerrar el cursor quema los conjuntos de resultados restantes, si los hay, y desactiva el cursor. De la fuente del cursor :
def close(self):
"""Close the cursor. No further queries will be possible."""
if not self.connection: return
while self.nextset(): pass
self.connection = None
Sería tan fácil cerrar el cursor al salir, así que debo suponer que no se ha hecho a propósito. Por otro lado, podemos ver que cuando se elimina un cursor, se cierra de todos modos, por lo que supongo que el recolector de basura eventualmente lo logrará. No sé mucho sobre la recolección de basura en Python.
def __del__(self):
self.close()
self.errorhandler = None
self._result = None
Otra conjetura es que puede haber una situación en la que desee reutilizar el cursor después del bloque with
. Pero no puedo pensar en ninguna razón por la que necesitarías hacer esto. ¿No siempre puede terminar de usar el cursor dentro de su contexto, y simplemente usar un contexto separado para la próxima transacción?
Para ser muy claro, este ejemplo obviamente no tiene sentido:
with conn as cursor:
cursor.execute(select_stmt)
rows = cursor.fetchall()
Debería ser:
with conn as cursor:
cursor.execute(select_stmt)
rows = cursor.fetchall()
Tampoco este ejemplo tiene sentido:
# first transaction
with conn as cursor:
cursor.execute(update_stmt_1)
# second transaction, reusing cursor
try:
cursor.execute(update_stmt_2)
except:
conn.rollback()
else:
conn.commit()
Simplemente debería ser:
# first transaction
with conn as cursor:
cursor.execute(update_stmt_1)
# second transaction, new cursor
with conn as cursor:
cursor.execute(update_stmt_2)
Nuevamente, ¿cuál sería el daño al cerrar el cursor al salir, y qué beneficios hay para no cerrarlo?
Para responder a su pregunta directamente: No puedo ver ningún daño en el cierre al final de un bloque. No puedo decir por qué no se hace en este caso. Pero, como hay una escasez de actividad en esta pregunta, realicé una búsqueda en el historial del código y haré algunas reflexiones ( conjeturas ) sobre por qué no se puede llamar al close()
:
Existe una pequeña posibilidad de que girar a través de las llamadas a
nextset()
puedanextset()
una excepción, posiblemente se haya observado y se haya visto como no deseable. Esta puede ser la razón por la que la versión más reciente decursors.py
contiene esta estructura enclose()
:def close(self): """Close the cursor. No further queries will be possible.""" if not self.connection: return self._flush() try: while self.nextset(): pass except: pass self.connection = None
Existe la posibilidad (algo remota) de que podría llevar algún tiempo repasar todos los resultados restantes sin hacer nada. Por
close()
tanto, puede que no se llame aclose()
para evitar hacer algunas iteraciones innecesarias. Supongo que si cree que vale la pena guardar esos ciclos de reloj es subjetivo, pero podría argumentar que "si no es necesario, no lo haga".Al navegar por las confirmaciones de sourceforge, la funcionalidad fue agregada a la troncal por esta confirmación en 2007 y parece que esta sección de
connections.py
no ha cambiado desde entonces. Esa es una fusión basada en este compromiso , que tiene el mensajeAgregue el soporte de Python-2.5 para con la declaración como se describe en http://docs.python.org/whatsnew/pep-343.html Por favor, pruebe
Y el código que usted cita nunca ha cambiado desde entonces.
Esto me lleva a mi último pensamiento: probablemente solo sea un primer intento / prototipo que simplemente funcionó y, por lo tanto, nunca se cambió.
Versión más moderna
Usted vincula a la fuente para una versión heredada del conector. Observo que hay una bifurcación más activa de la misma biblioteca here , a la que me vinculo en mis comentarios sobre "versión más reciente" en el punto 1.
Tenga en cuenta que la versión más reciente de este módulo ha implementado __enter__()
y __exit__()
dentro del cursor
: vea aquí . __exit__()
aquí llama self.close()
y quizás esto proporciona una forma más estándar de usar la sintaxis, por ejemplo
with conn.cursor() as c:
#Do your thing with the cursor
Notas finales
NB : Supongo que debería agregar, en la medida en que entiendo la recolección de basura (tampoco soy un experto) una vez que no haya referencias a conn
, será desasignada. En este punto no habrá referencias al objeto del cursor y también se desasignará.
Sin embargo, llamar a cursor.close()
no significa que será recolectado como basura. Simplemente quema los resultados y establece la conexión en None
. Esto significa que no se puede reutilizar, pero no se recolectará de inmediato. Puede convencerse de ello llamando manualmente a cursor.close()
después de su bloque with
y luego, digamos, imprimiendo algún atributo del cursor
NB 2 Creo que este es un uso un tanto inusual de la sintaxis with
el objeto conn
porque persiste porque ya está en el ámbito externo; a diferencia de, digamos, lo más común es with open(''filename'') as f:
donde no hay objetos dando vueltas con referencias después del final del bloque with
.