Invece di chiedere qual è la pratica standard, dal momento che spesso non è chiara e soggettiva, potresti provare a guardare il modulo stesso per una guida. In generale, usando il with
parola chiave suggerita da un altro utente è un'ottima idea, ma in questa specifica circostanza potrebbe non darti le funzionalità che ti aspetti.
A partire dalla versione 1.2.5 del modulo, MySQLdb.Connection
implementa il protocollo di gestione del contesto
con il codice seguente (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()
Esistono diverse domande e risposte su with
già, oppure puoi leggere Capire l'istruzione "with" di Python
, ma essenzialmente quello che succede è che __enter__
viene eseguito all'inizio di with
blocco e __exit__
viene eseguito all'uscita da with
bloccare. Puoi usare la sintassi opzionale with EXPR as VAR
per associare l'oggetto restituito da __enter__
a un nome se intendi fare riferimento a quell'oggetto in seguito. Quindi, data l'implementazione di cui sopra, ecco un modo semplice per interrogare il tuo database:
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,),)"
La domanda ora è, quali sono gli stati della connessione e del cursore dopo essere usciti da with
bloccare? Il __exit__
il metodo mostrato sopra chiama solo self.rollback()
o self.commit()
, e nessuno di questi metodi continua a chiamare close()
metodo. Il cursore stesso non ha __exit__
metodo definito – e non avrebbe importanza se lo facesse, perché with
gestisce solo la connessione. Pertanto, sia la connessione che il cursore rimangono aperti dopo essere usciti dal with
bloccare. Ciò è facilmente confermabile aggiungendo il seguente codice all'esempio precedente:
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'
Dovresti vedere l'output "cursore aperto; connessione aperta" stampato su stdout.
Credo che tu debba chiudere il cursore prima di confermare la connessione.
Come mai? L'API MySQL C
, che è la base per MySQLdb
, non implementa alcun oggetto cursore, come implicito nella documentazione del modulo:"MySQL non supporta i cursori; tuttavia, i cursori sono facilmente emulabili."
Infatti, il MySQLdb.cursors.BaseCursor
la classe eredita direttamente da object
e non impone tale restrizione ai cursori per quanto riguarda il commit/rollback. Uno sviluppatore Oracle ha detto questo
:
cnx.commit() prima di cur.close() mi sembra più logico. Forse puoi seguire la regola:"Chiudi il cursore se non ne hai più bisogno." Quindi commit() prima di chiudere il cursore. Alla fine, per Connector/Python, non fa molta differenza, ma o per altri database potrebbe.
Mi aspetto che sia il più vicino possibile alla "prassi standard" su questo argomento.
C'è qualche vantaggio significativo nel trovare insiemi di transazioni che non richiedono commit intermedi in modo da non dover ottenere nuovi cursori per ogni transazione?
Ne dubito fortemente e, nel tentativo di farlo, potresti introdurre un ulteriore errore umano. Meglio decidere una convenzione e attenersi ad essa.
C'è molto sovraccarico per ottenere nuovi cursori o semplicemente non è un grosso problema?
L'overhead è trascurabile e non tocca affatto il server del database; è interamente all'interno dell'implementazione di MySQLdb. Puoi guardare BaseCursor.__init__
su github
se sei davvero curioso di sapere cosa succede quando crei un nuovo cursore.
Tornando a quando stavamo discutendo di with
, forse ora puoi capire perché MySQLdb.Connection
classe __enter__
e __exit__
i metodi ti danno un nuovo oggetto cursore in ogni with
blocco e non preoccuparti di tenerne traccia o chiuderlo alla fine del blocco. È abbastanza leggero ed esiste esclusivamente per la tua comodità.
Se è davvero così importante per te microgestire l'oggetto cursore, puoi usare contextlib.closing
per compensare il fatto che l'oggetto cursore non ha __exit__
definito metodo. Del resto, puoi anche usarlo per forzare la chiusura dell'oggetto connessione all'uscita da un with
bloccare. Questo dovrebbe restituire "my_curs è chiuso; my_conn è chiuso":
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'
Nota che with closing(arg_obj)
non chiamerà __enter__
dell'oggetto argomento e __exit__
metodi; sarà solo chiama close
dell'oggetto argomento metodo alla fine del with
bloccare. (Per vederlo in azione, definisci semplicemente una classe Foo
con __enter__
, __exit__
e close
metodi contenenti una semplice print
e confronta cosa succede quando esegui with Foo(): pass
a cosa succede quando fai with closing(Foo()): pass
.) Ciò ha due implicazioni significative:
Innanzitutto, se la modalità autocommit è abilitata, MySQLdb BEGIN
una transazione esplicita sul server quando utilizzi with connection
e eseguire il commit o il rollback della transazione alla fine del blocco. Questi sono comportamenti predefiniti di MySQLdb, intesi a proteggerti dal comportamento predefinito di MySQL di eseguire immediatamente il commit di tutte le istruzioni DML. MySQLdb presuppone che quando utilizzi un gestore di contesto, desideri una transazione e utilizza l'esplicito BEGIN
per ignorare l'impostazione di autocommit sul server. Se sei abituato a usare with connection
, potresti pensare che l'autocommit sia disabilitato quando in realtà veniva solo bypassato. Potresti ricevere una spiacevole sorpresa se aggiungi closing
al tuo codice e perdere l'integrità transazionale; non sarai in grado di ripristinare le modifiche, potresti iniziare a vedere bug di simultaneità e potrebbe non essere immediatamente ovvio il motivo.
Secondo, with closing(MySQLdb.connect(user, pass)) as VAR
associa l'oggetto di connessione a VAR
, in contrasto con with MySQLdb.connect(user, pass) as VAR
, che associa un nuovo oggetto cursore a VAR
. In quest'ultimo caso non avresti accesso diretto all'oggetto connessione! Invece, dovresti usare la connection
del cursore attributo, che fornisce l'accesso proxy alla connessione originale. Quando il cursore è chiuso, la sua connection
l'attributo è impostato su None
. Ciò si traduce in una connessione abbandonata che rimarrà attiva fino a quando non si verifica uno dei seguenti eventi:
- Tutti i riferimenti al cursore vengono rimossi
- Il cursore esce dall'ambito
- La connessione è scaduta
- La connessione viene chiusa manualmente tramite gli strumenti di amministrazione del server
Puoi verificarlo monitorando le connessioni aperte (in Workbench o utilizzando SHOW PROCESSLIST
) mentre si eseguono le seguenti righe una per una:
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