Python 为什么为 sqlite3 游标编写上下文管理器不起作用?



这应该是有效的,但只是说没有库存表-应该在contextmanager中的某个地方丢失了连接?

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

问题在于使用上下文管理器的方式。调用doquery只需创建一个上下文管理器对象——您需要在with语句中使用它,该语句会根据需要调用其__enter____exit__方法。例如,尝试以下操作:

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)

我得到的输出是:

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

您可能需要重新阅读有关with语句和contextlib的文档。

代码的另一个问题是,如果c.executeconn.commit引发异常,则不会调用c.close——我不知道这是否真的有必要,但据推测,这正是您最初想要使用上下文管理器而不是函数的原因。以下更改应该可以解决这两个问题:

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)

然而,我不认为这是最干净的方式。就我所见,没有理由创建三个单独的cursor对象——您可以为每个查询使用相同的对象。我认为对conn.commit的调用实际上也没有必要——使用数据库连接作为上下文管理器将自动提交事务,或者在引发异常时回滚事务(请参阅sqlite3模块文档)。

编辑:这是一个更干净的版本,它仍然有效。不过,我真的不知道关闭光标到底能做什么——这可能没有必要(Cursor.close似乎甚至没有文档记录)。

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)

似乎yield正在干扰create tableinsert into语句。

在以下内容中,除了select之外,我不使用yield,而且它工作正常:

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

最新更新