First, what is a python sqlite3.Cursor
? The
code
shows it is essentially a holder for a sqlite3_stmt
structure, a prepared
statement handle which is passed to
sqlite3_step
to iterate through
result rows.
As far as I can tell, there’s no benefit to creating your own cursor object
with conn.cursor()
over just using the one automatically created for you
and returned by
conn.execute()
.
Skip conn.cursor()
and save yourself the extra line of code each time.
(This is sqlite-specific advice; there may be reasons to create your own
cursors with other DB-API drivers for other database systems, I don’t
know.)
What does cursor.close()
do?
static PyObject *
pysqlite_cursor_close_impl(pysqlite_Cursor *self)
{
⋮
if (self->statement) {
(void)stmt_reset(self->statement);
Py_CLEAR(self->statement);
⋮
}
The sqlite
docs
give a short description of sqlite3_reset
, which is what stmt_reset
calls:
6. Binding Parameters and Reusing Prepared Statements
… SQLite allows the same prepared statement to be evaluated multiple
times. This is accomplished using the following routines:
After a prepared statement has been evaluated by one or more calls to
sqlite3_step(), it can be reset in order to be evaluated again by a call
to sqlite3_reset(). Think of sqlite3_reset() as rewinding the
prepared statement program back to the beginning.
Closing a cursor causes Python to tell the underlying sqlite library to
discard the associated result set. But Python still keeps the
sqlite3_stmt
structure around, because Python internally maintains a
cache of prepared statements.
Code like the following is generally fine on CPython:
for row in conn.execute("SELECT …"):
do_stuff_with(r)
This is because:
When iteration gets to the end of the result set, python will
automatically call stmt_reset
to discard the result
set.
This happens with both standard python iteration protocols like for … in …
, and the
sqlite3.Cursor.fetch*
methods.
If the loop exits early, e.g., because an exception was raised,
CPython’s reference-counting will trigger finalization of the anonymous cursor
object, again calling stmt_reset
. Other python implementations may differ.
Given the above, while it is unlikely that you will ever run into a memory leak by not
closing your cursors, it is definitely possible if you have cursors that
are both:
- only partially-iterated, and
- kept in scope for long periods of time.
Thus for portability and explicitness you may want to write:
from contextlib import closing
with closing(conn.execute("SELECT …")) as cursor:
for row in cursor:
…