Python 中 sqlite3 模块使用小记
Posted on 日 12 五月 2013 in python • 2 min read
前记
Python 的标准库中包含了对 sqlite 这个轻巧的数据库的支持模块,也就是 sqlite3 模块。sqlite 数据库的好处我就不多说了,小型而强大,适合很多小型或者中型的数据库应用。最近在使用 sqlite3 模块遇到一些问题,解决了,顺便就记下来。
问题
sqlite3 模块的使用很简单,如下这段测试代码,创建一个 person 数据表然后进行一次数据库查询操作。
#!/usr/bin/env pypthon
#_*_ coding: utf-8 _*_
import sqlite3
SCHEMA = """
CREATE TABLE person (
p_id int,
p_name text
)
"""
def init():
data = [(1, 'tony'), (1, 'jack')]
conn = sqlite3.connect(':memory:')
c = conn.cursor()
try:
c.execute(SCHEMA)
for person in data:
c.execute('insert into person values(?, ?)', person)
conn.commit()
except sqlite3.Error as e:
print 'error!', e.args[0]
return conn
if __name__ =='__main__':
conn = init()
c = conn.cursor()
#Do a query.
c.execute('select * from person where p_name = ?', 'tony')
person = c.fetchone()
print person
运行这段代码,抛出了个异常,如下提示:
Traceback (most recent call last):
File "sqlite3_test.py", line 32, in <module>
c.execute('select * from person where p_name = ?', 'tony')
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 4 supplied.
很莫名奇妙是不?明明我提供的占位符?绑定只有一个字符串参数,可是却说我提供了四个。再看仔细点,说提供了四个,正好字符串 'tony' 是四个字符。
解决
翻了翻文档,发现也给出了一个占位符查询的例子如下:
t = (’RHAT’,)
c.execute(’SELECT * FROM stocks WHERE symbol=?’, t)
所以将字符参数放到元组中就可以了,修改如下:
c.execute('select * from person where p_name = ?', ('tony'))
结果依旧是抛出了同样的异常。再仔细看下,漏了个',',于是加上:
c.execute('select * from person where p_name = ?', ('tony',))
这次终于得到最终的结果了,其中的字符为 unicode 类型:
(1, u'tony')
原因
但是为什么? Python 中的 sqlite3 模块提供了对 sqlite 数据操作的 API,执行查询的函数是在 sqlite3 模块源码中定义的,很明显想要知道为啥,最好的方式是去看 sqlite3 模块的源码。我手上的 Python 源码是 Python-2.7.4,在源码 Python-2.7.4/Modules/_sqlite/cursor.c 的函数 PyObject _pysqlite_query_execute(pysqlite_Cursor self, int multiple, PyObject* args) 中 497-529 行:
...
/* execute() */
if (!PyArg_ParseTuple(args, "O|O", &operation, &second_argument)) {
goto error;
}
if (!PyString_Check(operation) && !PyUnicode_Check(operation)) {
PyErr_SetString(PyExc_ValueError, "operation parameter must be str or unicode");
goto error;
}
parameters_list = PyList_New(0);
if (!parameters_list) {
goto error;
}
if (second_argument == NULL) {
second_argument = PyTuple_New(0);
if (!second_argument) {
goto error;
}
} else {
Py_INCREF(second_argument);
}
if (PyList_Append(parameters_list, second_argument) != 0) {
Py_DECREF(second_argument);
goto error;
}
Py_DECREF(second_argument);
parameters_iter = PyObject_GetIter(parameters_list);
if (!parameters_iter) {
goto error;
}
...
从这段源码中可以看到这段代码将参数 args 解析成为 Python 的一个元组作为 parameters_list ,然后最这个得到的元组进行 iter 操作,不断地读取这个元组的元素作为参数,而 Python 中对一个字符串进行 parse tuple 会怎样?我觉得 PyArg_ParseTuple 这个函数的操作和以下代码会是类似的:
...
>>> tuple('test')
('t', 'e', 's', 't')
...
所以现在我们可以看到我们的答案了,sqlite3 模块中,cursor 对象的 execute 方法会接受两个参数,第二个参数会被 PyArg_ParseTuple 函数转化成为 Python中 的 tuple。而对一个字符进行 tuple parse 导致的结果是将这个字符串的每个字符作为 tuple 的一个元素,所以上面抛出错误的时候提示的提供了四个所以错误也可以理解了。那为什么'('tony')'同样是错误呢?如下:
>>> type(('tony'))
<type 'str'>
>>> type(('tony',))
<type 'tuple'>
很明显,('tony')是一个 str 也就是一个字符串,相当于是 'tony',而 ('tony',) 才是一个单元素的 tuple 。同样,因为:
>>> tuple(['tony'])
('tony',)
所以如果那一行查询执行改为:
c.execute('select * from person where p_name = ?', ['tony'])
同样也是可以执行成功的。