2013-03-22 7 views
5

Sfondo

Recentemente ho scoperto la parola chiave di Python with e ha iniziato a vedere la sua potenziale utilità per la gestione di più prettily alcuni scenari in cui avevo in precedenza ho usato try: ... finally: ... costrutti. Ho deciso subito di provarlo sull'oggetto di connessione MySQLdb in un codice che stavo scrivendo.Il comportamento __enter__ e __exit__ per gli oggetti di connessione specificati nell'API del database Python?

non ho disturbato la lettura su come __enter__ e __exit__ si comportano in implementatori del database API di Python, e ingenuamente previsto il comportamento di essere come quella degli oggetti di file - tutto mi aspettavo è stato per l'uscita di chiamare connection.close().

immaginare la mia confusione, allora, a questo comportamento:

>>> with util.get_db_connection() as conn: 
...  print conn 
... 
<MySQLdb.cursors.Cursor object at 0xb6ca8b4c> 

get_db_connection() restituisce un oggetto di connessione MySQLdb, ma il metodo di tale oggetto di connessione __enter__ restituisce un oggetto del cursore, non l'oggetto di connessione in sé come mi aspettavo dato come __enter__ e __exit__ funzionano per gli oggetti file. Credo che dovrei fare with util.get_db_connection() as cursor:, altrimenti non usare with affatto.

Domande

immediatamente questa scoperta mi domando alcune cose:

  1. Che altro fare il __enter__ e __exit__ metodi di MySQLdb oggetti di connessione fare? __exit__ sta per eseguire magicamente il commit o il rollback delle modifiche per me senza che io chieda esplicitamente che ciò accada? C'è qualcos'altro non ovvio che dovrei sapere?
  2. Questo comportamento è lo stesso in altri implementatori dell'API del database Python (come sqlite3, django o psycopg2)?
  3. Questo comportamento è formalmente speculato ovunque? ctrl-f l'ultima specifica (PEP 249 -- Python Database API Specification v2.0) per "invio", "uscita" e "gestore contesto" non genera nulla.

risposta

15

Il DBAPI Python è stato scritto molto prima che i gestori di contesto fossero aggiunti al linguaggio Python.

Come tali, diverse librerie di database hanno preso le proprie decisioni su come implementare il supporto del gestore di contesto (se lo hanno implementato del tutto).

Solitamente utilizzando il database come gestore del contesto si lega a una transazione. La transazione viene avviata su __enter__ e commessa o interrotta su __exit__, a seconda che sia presente o meno un'eccezione. Come tale, si suppone di utilizzare la connessione MySQL come contesto responsabile dopo la connessione separatamente:

connection = util.get_db_connection() 

with connection as cursor: 
    cursor.execute(...) 

# connection commit is issued if no exceptions were raised. 

Il sqlite3 context manager implementation è leggermente diversa; gestisce anche le operazioni, ma non restituisce un cursore dal metodo __enter__:

con = sqlite3.connect(":memory:") 
with con: 
    cursor = con.cursor() 
    # or use the connection directly 
    con.execute(...) 

Tecnicamente, appena ritorna self su __enter__.

+0

Per MySQL, faccio spesso 'con contextmanager.closing (db()) come d, d come c:' per ottenere la chiusura automatica * e * una transazione. – glglgl