2010-05-14 11 views
14

Chcę zapobiec jak największemu otwarciu połączenia z bazą danych, ponieważ ten kod będzie działał na intensywnie używanym serwerze, a ludzie tutaj już mi powiedzieli, że połączenia z bazą danych powinny być zawsze zamykane tak szybko, jak to możliwe.W Pythonie, jak upewnić się, że połączenie z bazą danych zawsze się zamknie przed opuszczeniem bloku kodu?

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 

Wierzę, że pozostawia otwarte połączenie z bazą danych, gdy nie ma rząd na stole, tho I'm still really not sure how it works.

W każdym razie, może to jest zły projekt w tym sensie, że mogę otworzyć i zamknąć połączenie DB po każdym małym bloku z execute. I na pewno, mogę tylko dodać close tuż przed return w takim razie ...

Ale jak zawsze mogłem prawidłowo zamknąć DB bez konieczności martwienia się, czy mam to return albo raise lub continue lub cokolwiek w środku? Zastanawiam się w coś w rodzaju bloku kodu, podobnie jak przy użyciu try, jak w poniższym sugestii, co oczywiście nie działa:

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 

I nie sądzę, że istnieje coś podobnego do ExitingCodeBlock wyjątek , tho wiem, że jest spróbowaćelse, ale mam nadzieję, że Python już podobną funkcję ...

a może ktoś może zaproponować mi ruch paradygmatu i powiedz mi, że to jest okropne i bardzo mi doradzić, aby nie zrobić że. Może to po prostu coś, o co nie należy się martwić i czy MySQLdb sobie z tym poradzi?

Odpowiedz

20

Tradycyjne podejście jest oświadczenie 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) 

Od Pythonie 2.6 (i 2.5 z from __future__ import with_statement), istnieje alternatywa (chociaż try/finally nadal działa doskonale!): Instrukcja with.

with somecontext as whatever: 
    # the work goes here 

Kontekst ma metodę __enter__, wykonywane na wejściu (do zwrotu whatever powyżej, jeżeli chcesz) i metodą __exit__, wykonywane na wyjściu. Pomimo elegancji, ponieważ nie ma istniejącego kontekstu, który działa tak, jak chcesz, praca niezbędna do jego zbudowania (chociaż zmniejszona w 2.6 z contextlib) powinna prawdopodobnie sugerować, że stara dobra/dobra jest najlepsza.

Jeśli masz 2.6 i chcesz spróbować contextlib, to jest jeden sposób, by to zrobić, aby "ukryć" try/finally ...:

import contextlib 

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

być używany jako:

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_ 

Może warto, jeśli zamierzasz używać tego wiele, wiele razy, aby uniknąć wielokrotnego powtarzania/sprawdzania dla każdego z tych wielu zastosowań.

+0

cóż, po tym, jak ty i Michael zredagowaliście swoje odpowiedzi, mamy 2 bardzo podobne i kompletne ... trudne do wybrania. – cregox

6

Jeśli MySQLdb obsługuje to, możesz użyć instrukcji "with". Instrukcja "with" istnieje właśnie z tego powodu. Jednak wymaga to zdefiniowania obiektu __enter__ i __exit__, aby to działało.

Jako przykład instrukcji with ... do odczytu/zapisu plików, możesz mieć:

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

Jeśli tego nie obsługuje, to zawsze można użyć try ... finally jako w:

try: 
    # Do some processing 
finally: 
    # Cleanup 

klauzula finally jest wykonywany bez względu na to, w jaki sposób wykończenia try (czy to zakończone Udane lub wyjątek została propagowana ale złapany, albo wyjątek i będzie nadal propagować).

+0

Niedawno wypróbowałem to z pyodbc i nie zadziałało, ale możesz mieć więcej szczęścia. –

+1

Nie powinno być zbyt trudne, aby autor wywoływał odpowiednie wywołania w opakowaniu, jeśli dany obiekt go już nie obsługuje. Cytowany obrazek pokazuje nawet, jak to zrobić w przypadku plików. –

+1

To bardzo interesujące, ale nie sądzę, że jest obsługiwane przez MySQLdb. Zdecydowanie nie jest to Python 2.5 – cregox

Powiązane problemy