2010-05-14 16 views
14

Voglio impedire che la connessione al database sia aperta il più possibile, perché questo codice verrà eseguito su un server ad uso intensivo e le persone mi hanno già detto che le connessioni al database dovrebbero essere sempre chiuse il prima possibile.In Python, come assicurarsi che la connessione al database si chiuda sempre prima di lasciare un blocco di codice?

def do_something_that_needs_database(): 
    dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor) 
    dbCursor = dbConnection.cursor() 
    dbCursor.execute('SELECT COUNT(*) total FROM table') 
    row = dbCursor.fetchone() 
    if row['total'] == 0: 
     print 'error: table have no records' 
     dbCursor.execute('UPDATE table SET field="%s"', whatever_value) 
     return None 
    print 'table is ok' 
    dbCursor.execute('UPDATE table SET field="%s"', another_value) 

    # a lot more of workflow done here 

    dbConnection.close() 

    # even more stuff would come below 

credo che lascia una connessione al database aperto quando non c'è fila sul tavolo, tho I'm still really not sure how it works.

In ogni caso, forse questo è un cattivo progetto nel senso che potrei aprire e chiudere una connessione DB dopo ogni piccolo blocco di execute. E certo, ho potuto solo aggiungere un close a destra prima del return in questo caso ...

Ma come potevo sempre correttamente chiudere il DB senza doversi preoccupare se ho che return, o un raise, o continue, o qualunque cosa nel mezzo? Sto pensando a qualcosa di simile a un blocco di codice, simile ad usare try, come nel seguente suggerimento, che ovviamente non funziona:

def do_something_that_needs_database(): 
    dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor) 
    try: 
     dbCursor = dbConnection.cursor() 
     dbCursor.execute('SELECT COUNT(*) total FROM table') 
     row = dbCursor.fetchone() 
     if row['total'] == 0: 
      print 'error: table have no records' 
      dbCursor.execute('UPDATE table SET field="%s"', whatever_value) 
      return None 
     print 'table is ok' 
     dbCursor.execute('UPDATE table SET field="%s"', another_value) 
     # again, that same lot of line codes done here 
    except ExitingCodeBlock: 
     closeDb(dbConnection) 
    # still, that "even more stuff" from before would come below 

Non credo che ci sia qualcosa di simile a ExitingCodeBlock un'eccezione , tho so che c'è la provareelse, ma spero Python già una funzionalità simile ...

O forse qualcuno mi può suggerire una mossa paradigma e mi dicono questo è terribile e altamente consigliarmi di non faccio mai quella. Forse questo è solo qualcosa di cui non ti preoccupare e lascia che MySQLdb lo gestisca, o è così?

risposta

20

L'approccio tradizionale è la dichiarazione try/finally:

def do_something_that_needs_database(): 
    dbConnection = MySQLdb.connect(host=args['database_host'], user=args['database_user'], passwd=args['database_pass'], db=args['database_tabl'], cursorclass=MySQLdb.cursors.DictCursor) 
    try: 
     # as much work as you want, including return, raising exceptions, _whatever_ 
    finally: 
     closeDb(dbConnection) 

Dal momento che Python 2.6 (e 2.5 con un from __future__ import with_statement), c'è un'alternativa (anche se try/finally funziona ancora perfettamente!): L'istruzione with.

with somecontext as whatever: 
    # the work goes here 

Un contesto ha un metodo __enter__, eseguito in entrata (per tornare alla whatever sopra, se volete) e un metodo di __exit__, eseguito in uscita. Nonostante l'eleganza, dal momento che non esiste un contesto che funzioni nel modo desiderato, il lavoro necessario per costruirne uno (anche se ridotto in 2.6 con contextlib) dovrebbe probabilmente suggerire che il buon vecchio tentativo/finalmente sia il migliore.

Se si dispone di 2.6 e volete provare contextlib, questo è un modo che si possa fare a "nascondere" il try/finally ...:

import contextlib 

@contextlib.contextmanager 
def dbconnect(**kwds): 
    dbConnection = MySQLdb.connect(**kwds) 
    try: 
    yield dbConnection 
    finally: 
    closeDb(dbConnection) 

da utilizzare come:

def do_something_that_needs_database(): 
    with dbconnect(host=args['database_host'], user=args['database_user'], 
        passwd=args['database_pass'], db=args['database_tabl'], 
        cursorclass=MySQLdb.cursors.DictCursor) as dbConnection: 
     # as much work as you want, including return, raising exceptions, _whatever_ 

Potrebbe valerne la pena se si userà questo, molte volte, solo per evitare di ripetere il tentativo/finalmente più e più volte per ciascuno di questi molti usi.

+0

oh bene, dopo che tu e Michael avete modificato le vostre risposte ne abbiamo 2 molto simili e completi ... difficili da scegliere. – cregox

6

Se MySQLdb lo supporta, è possibile utilizzare l'istruzione "with". L'affermazione "with" esiste proprio per questa ragione. Tuttavia, richiede che l'oggetto definisca __enter__ e __exit__ affinché funzioni.

Come esempio l'istruzione with ... per la lettura/scrittura di file, si potrebbe avere:

with open('filename','r') as file: 
    for line in file: 
     # processing.... 
# File automatically closed afterwards or if there was an exception thrown 

Se non lo supporta, allora si può sempre usare try ... finally come in:

try: 
    # Do some processing 
finally: 
    # Cleanup 

La clausola finally viene eseguita, non importa quanto le finiture provare (se è completata successo, o un eccezione è stata propagata ma catturato, o un'eccezione è stato gettato e continuerà a propagarsi).

+0

Recentemente l'ho provato con pyodbc e non ha funzionato, ma potresti avere più fortuna. –

+1

Non dovrebbe essere troppo difficile per creare le chiamate appropriate in un wrapper, se il tuo oggetto particolare non lo supporta già. Il citato pep mostra anche come farlo per i file. –

+1

È molto interessante sapere, ma non penso che sia supportato da MySQLdb. Non è sicuramente da Python 2.5 – cregox

Problemi correlati