此外,Python文档提供了一个光标使用示例,并说:“如果我们完成了工作,我们也可以关闭光标。” 关键词是“可以”,而不是“必须”。他们的意思是什么?
这可能是个好主意(尽管在sqlite上可能并不重要,我不确定),但它会使您的代码更具可移植性。此外,对于最近版本的Python(2.5+),这很容易实现:
from __future__ import with_statement
from contextlib import closing
with closing(db.cursor()) as cursor:
# do some stuff
您不必调用close()
关闭光标;它可以像其他对象一样被垃圾回收。
但即使等待垃圾回收听起来没问题,我认为仍然有好的风格是确保诸如数据库游标之类的资源在是否出现异常的情况下都能得到关闭。
大家好,
我使用sqlite3和Python 3.8编写代码时遇到了逐渐增加的内存泄漏问题。我追踪了问题的根源并发现是我的数据库类,我打开了一个游标但从未关闭它。数据库在程序(Windows服务)运行期间一直保持打开状态,并在退出时关闭。
在我开始关闭所有使用游标的db中的游标之后,我的内存泄漏停止了,并且内存足迹变得稳定。
因此,我建议你花时间关闭你的游标。这将使代码更加一致,而且显然有助于控制内存消耗。
以下是我如何关闭游标的示例:
def write_to_db(self, cache_item:CacheEntry):
'''Write a single cache entry to the database'''
crsr = self._db_con.cursor()
# Load some data elements
fax_line_path = cache_item._dir_part
phone_line = cache_item._phone_line
sub_folder = cache_item._subfolder
fname = cache_item._fname
work_done = cache_item.get_workdone()
try:
crsr.execute(FilenameCacheDB.INSERT_CACHE,
(fax_line_path,
phone_line,
sub_folder,
fname,
work_done))
except Exception as e:
LOG.warning(f"Could not write {cache_item} to db because {e}")
raise e
finally:
#
# I was *not* closing the cursor prior
#
crsr.close()
self._db_con.commit()
这段代码将自动关闭Cursor
。它还将自动关闭和提交Connection
。
import sqlite3
import contextlib
def execute_statement(statement):
with contextlib.closing(sqlite3.connect(path_to_file)) as conn: # auto-closes
with conn: # auto-commits
with contextlib.closing(conn.cursor()) as cursor: # auto-closes
cursor.execute(statement)
.close()
方法。但是Python 3.5和3.6文档描述了光标.close()
方法:
立即关闭光标(而不是在调用
__del__
时关闭)。从此时起,光标将无法使用,如果尝试使用光标进行任何操作,将引发
ProgrammingError
异常。
我还没有看到 sqlite3.Cursor.close()
操作产生任何效果。
关闭之后,你仍然可以调用 fetch(all|one|many)
方法,它会返回上一次执行语句剩余的结果。甚至运行 Cursor.execute()
也仍然有效...
sqlite3.ProgrammingError: Cannot operate on a closed cursor.
- andrewdotn看着stackoverflowuser2010和Peer提供的代码片段和想法,使用Python上下文管理器优雅地处理游标更加容易。
from contextlib import contextmanager
@contextmanager
def OpenCursor(conn):
cursor = conn.cursor()
try:
yield (cursor)
except Exception as e:
cursor.close()
raise e
else:
cursor.close()
不使用OpenCursor的用法:
def get(conn, key, default=None):
cursor = conn.cursor()
cursor.execute(f'SELECT value FROM table WHERE key=?', (key,))
row = cursor.fetchone()
if row:
return (True)
else:
return (default)
在使用OpenCursor作为上下文管理器时的用法:
def get(conn, key, default=None):
with OpenCursor(conn) as cursor:
cursor.execute(f'SELECT value FROM table WHERE key=?', (key,))
row = cursor.fetchone()
if row:
return (True)
else:
return (default)
finally: cursor.close()
替代 except/else
会使 OpenCursor()
的代码变得更简单(也更安全)。实际上,整个函数可以成为一个一行代码的函数,不需要装饰器,并且具有完全相同的功能:def OpenCursor(conn): return contextlib.closing(conn.cursor())
。 - MestreLion首先,什么是Python的sqlite3.Cursor
?代码显示它本质上是一个sqlite3_stmt
结构的持有者,这是一个准备好的语句句柄,它被传递给sqlite3_step
来迭代结果行。
conn.execute()
自动创建并返回的游标对象,与使用 conn.cursor()
创建自己的游标对象相比没有任何好处。跳过 conn.cursor()
,这样每次可以节省一行代码。(这是特定于sqlite的建议;对于其他数据库系统的其他DB-API驱动程序,可能有创建自己的游标的原因,我不知道。)
static PyObject *
pysqlite_cursor_close_impl(pysqlite_Cursor *self)
{
⋮
if (self->statement) {
(void)stmt_reset(self->statement);
Py_CLEAR(self->statement);
⋮
}
SQLite文档简要介绍了sqlite3_reset
,它是stmt_reset
调用的函数:
SQLite 允许同一个预处理语句被多次执行。这可以通过以下例程实现:
在预处理语句经过一次或多次sqlite3_step()调用后,可以通过调用sqlite3_reset()将其重置以便再次执行。可以将sqlite3_reset()视为将预处理语句程序倒回到开头。
关闭游标会导致Python告诉底层的sqlite库丢弃相关的结果集。但是Python仍然保留sqlite3_stmt
结构,因为Python内部维护了一个准备语句的缓存。for row in conn.execute("SELECT …"):
do_stuff_with(r)
因此,为了实现可移植性和明确性,您可能希望编写以下代码:
from contextlib import closing
with closing(conn.execute("SELECT …")) as cursor:
for row in cursor:
…
是的,我们应该关闭我们的游标。我曾经遇到过一个错误,当我使用游标来配置我的连接对象时:'PRAGMA synchronous=off' 和 'PRAGMA journal_mode=off' 以加快插入速度。一旦我关闭了游标,错误消失了。我忘记了我遇到了什么类型的错误。
with con.cursor() as cursor: ...
?那样会更容易阅读。这段代码感觉有点像在编写Java程序... 嗯 /耸肩 - matthaeusconn = sqlite3.connect(...); cur = conn.cursor(); cur.execute(...); cur.close(); conn.commit(); conn.close()
更美观,而且更短、更安全。如果使用sqlite3的conn.execute()
快捷方式,它甚至可以更短。我也希望Python的DB API能够自动关闭Cursor
的上下文管理器。 - MestreLion