2012-04-05 15 views
7

Se supone que esto funciona pero solo dice que no hay existencias en la mesa, ¿se supone que perdió la conexión en alguna parte dentro del gestor de contexto?Python ¿por qué no funciona un gestor de contexto para un cursor 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 

Respuesta

15

el problema está en la forma en que se utiliza el gestor de contexto. Llamar al doquery simplemente crea un objeto de administrador de contexto; debe usarlo dentro de una declaración with, que llama a sus métodos __enter__ y __exit__ según corresponda. Por ejemplo, pruebe lo siguiente:

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) 

La salida lo que consigo es:

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

Es posible que desee volver a leer la documentación sobre the with statement y contextlib.

Otro problema con su código es que si c.execute o conn.commit lanza una excepción, c.close no se llamará - No sé si eso es realmente necesario, pero se supone que es la razón por la que desea utilizar un gestor de contexto en lugar de una función en primer lugar. Los siguientes cambios deberían solucionar ambos problemas:

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) 

Sin embargo, no creo que esta sea la forma más limpia de hacerlo. Por lo que puedo ver, no hay ninguna razón para crear tres objetos separados cursor; puede usar el mismo para cada consulta. No creo que la llamada al conn.commit sea realmente necesaria. Usar la conexión de la base de datos como gestor de contexto confirmará automáticamente las transacciones o las retrotraerá si se produce una excepción (ver sqlite3 module documentation).

EDITAR: Esta es una versión mucho más limpia, que todavía funciona. Realmente no sé lo que hace el cursor: probablemente no sea necesario (Cursor.close ni siquiera parece estar documentado).

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

Parece que yield está interfiriendo con la create table y las declaraciones insert into.

En la siguiente No consumo yield de excepción de la select y funciona bien:

#!/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

No tiene sentido utilizar el decorador 'contextmanager' en una función normal (en oposición a un [generador] (http://docs.python.org/reference/simple_stmts.html#yield)) El objetivo de un gestor de contexto es usarlo en una declaración con, y si intenta utilizar el valor de retorno de 'doquery2' en una sentencia with, obtendrá un' TypeError'. – James

+0

Simplemente estoy señalando dónde se rompe el código. La función doquery es AFAICT redundante. Si ve el alcance de la pregunta de forma menos estricta que yo, está bien, pero hasta donde yo sé, mi respuesta explica de la manera más simple por qué lo que el cartel menciona como no funciona no funciona. –

Cuestiones relacionadas