2012-04-05 12 views
7

To ma działać, ale po prostu nie ma tabeli zapasów - przypuszczalnie utracił połączenie gdzieś wewnątrz menedżera kontekstu?Python dlaczego nie pisze menedżera kontekstu dla kursora sqlite3?

import sqlite3 
from contextlib import contextmanager 

@contextmanager 
def doquery(conn, q, params=()): 
    c = conn.cursor() 
    c.execute(q, params) 
    conn.commit() 
    yield c  
    c.close() 

with sqlite3.connect(':memory:') as db:  
    doquery(db,'''create table stocks 
    (date text, trans text, symbol text, 
    qty real, price real)''') 

    doquery(db,"""insert into stocks 
      values ('2006-01-05','BUY','RHAT',100,35.14)""") 

    with doquery(db, 'select * from stocks') as r: 
     for row in r: 
      print row 

Odpowiedz

15

Problemem jest to ze sposobu korzystania z menedżera kontekstowe. Wywołanie doquery po prostu tworzy obiekt menedżera kontekstu - należy go użyć w ramach instrukcji with, która wywołuje odpowiednio metody i __exit__. Na przykład, spróbuj wykonać następujące czynności:

from contextlib import contextmanager 

@contextmanager 
def enter_exit(text): 
    print('entering') 
    yield text 
    print('exiting') 

print(enter_exit('attempt 1')) 

with enter_exit('attempt 2') as t: 
    print(t) 

Wyjście pojawia się:

<contextlib._GeneratorContextManager object at 0xcf3e90> 
entering 
attempt 2 
exiting 

Może chcesz ponownie przeczytać dokumentację o the with statement i contextlib.

Innym problemem z kodem jest, że jeśli c.execute lub conn.commit podnosi wyjątek, c.close nie można nazwać - nie wiem, czy to jest rzeczywiście konieczne, ale prawdopodobnie jest to powód, dla którego chcesz użyć menedżera kontekstowe zamiast funkcji w pierwszej kolejności. Poniższe zmiany powinny rozwiązać oba problemy:

import sqlite3 
from contextlib import contextmanager 

@contextmanager 
def doquery(conn, q, params=()): 
    c = conn.cursor() 
    try: 
     c.execute(q, params) 
     conn.commit() 
     yield c 
    finally: 
     c.close() 

with sqlite3.connect(':memory:') as db: 
    with doquery(db,'''create table stocks 
       (date text, trans text, symbol text, 
       qty real, price real)'''): 
     pass 

    with doquery(db,"""insert into stocks 
       values ('2006-01-05','BUY','RHAT',100,35.14)"""): 
     pass 

    with doquery(db, 'select * from stocks') as r: 
     for row in r: 
      print(row) 

Jednak nie sądzę, że jest to najczystszy sposób na zrobienie tego. O ile widzę, nie ma powodu, aby tworzyć trzy oddzielne obiekty cursor - możesz użyć tego samego dla każdego zapytania. Nie sądzę, aby połączenie z conn.commit było rzeczywiście konieczne - używając połączenia z bazą danych, ponieważ menedżer kontekstu automatycznie wykona transakcje lub wycofa je, jeśli zostanie zgłoszony wyjątek (patrz sqlite3 module documentation).

EDYCJA: Tutaj jest znacznie czystsza wersja, która nadal działa. Naprawdę nie wiem, co właściwie robi zamykanie kursora - prawdopodobnie nie jest to konieczne (Cursor.close nawet nie wydaje się być udokumentowane).

import sqlite3 
from contextlib import closing 

with sqlite3.connect(':memory:') as db: 
    with closing(db.cursor()) as c: 
     c.execute('''create table stocks 
       (date text, trans text, symbol text, 
       qty real, price real)''') 
     c.execute("""insert into stocks 
       values ('2006-01-05','BUY','RHAT',100,35.14)""") 
     c.execute('select * from stocks') 
     for row in c: 
      print(row) 
0

Wydaje się, że yield zakłóca create table oraz sprawozdania insert into.

W dalszej części nie używam yield za wyjątkiem select i działa ok:

#!/usr/bin/python3 
import sqlite3 
from contextlib import contextmanager 

@contextmanager 
def doquery(conn, q, params=()): 
    c = conn.cursor() 
    c.execute(q, params) 
    conn.commit() 
    yield c 
    c.close() 

@contextmanager 
def doquery2(conn, q, params=()): 
    c = conn.cursor() 
    c.execute(q, params) 
    conn.commit() 
    c.close() 

with sqlite3.connect(':memory:') as db: 
    doquery2(db,'''create table stocks 
    (date text, trans text, symbol text, 
    qty real, price real)''') 

    doquery2(db,"""insert into stocks                             
      values ('2006-01-05','BUY','RHAT',100,35.14)""") 

    with doquery(db, 'select * from stocks') as r: 
     for row in r: 
      print(row[0]) 
+0

To nie ma sensu używać 'contextmanager' dekorator na normalnej funkcji (w przeciwieństwie do prądnicy [] (http://docs.python.org/reference/simple_stmts.html#yield)). Cały punkt menedżera kontekstu polega na użyciu go w instrukcji with, a jeśli spróbujesz użyć zwracanej wartości 'doquery2' w instrukcji with, otrzymasz" TypeError ". – James

+0

Ja tylko wskazuję, gdzie kod się psuje. Funkcja doquery jest nadmiarowa. Jeśli widzisz, że zakres pytania jest mniej zawężony niż ja, to w porządku, ale o ile wiem, moja odpowiedź wyjaśnia w najprostszy sposób, dlaczego to, co ten plakat mówi, że nie działa, nie działa. –

Powiązane problemy