Python为什么不为sqlite3游标编写contextmanager?

use*_*373 7 python sqlite connection 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
Run Code Online (Sandbox Code Playgroud)

Jam*_*mes 15

问题在于您使用上下文管理器的方式.调用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)
Run Code Online (Sandbox Code Playgroud)

我得到的输出是:

<contextlib._GeneratorContextManager object at 0xcf3e90>
entering
attempt 2
exiting
Run Code Online (Sandbox Code Playgroud)

您可能希望重新阅读有关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)
Run Code Online (Sandbox Code Playgroud)

但是,我不认为这是最干净的方式.据我所知,没有理由创建三个单独的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)
Run Code Online (Sandbox Code Playgroud)