[英]Use the installed version of libsqlite3 from Python
Is it possible to use the installed version of SQLite3 from Python, with ctypes?是否可以使用 Python 中已安装的 SQLite3 版本和 ctypes? If so, how?
如果是这样,如何?
On a Mac, the below works without error:在 Mac 上,下面的操作没有错误:
from ctypes import CDLL
libsqlite3 = CDLL("libsqlite3.dylib")
... but then from https://www.sqlite.org/c3ref/sqlite3.html ...但随后来自https://www.sqlite.org/c3ref/sqlite3.html
Each open SQLite database is represented by a pointer to an instance of the opaque structure named "sqlite3".
每个打开的 SQLite 数据库都由一个指向名为“sqlite3”的不透明结构实例的指针表示。
(emphasis mine) (强调我的)
which to me suggests you can't really make a ctypes.Structure
for the database, say to then pass to sqlite3_open .这对我来说表明你不能真正为数据库制作一个
ctypes.Structure
,然后传递给sqlite3_open 。
(Context: I want to use parts of SQLite from Python that are not exposed by the built-in sqlite3 module) (上下文:我想使用 Python 中的 SQLite 的一部分,这些部分没有被内置的 sqlite3 模块公开)
The sqlite3
-API uses an opaque pointer, so in the end there is no need to know its memory layout - one just could use a void
-pointer. sqlite3
-API 使用不透明指针,因此最终无需知道其 memory 布局 - 只需使用void
-指针即可。
For example, opening a sqlite3
-database would create such a pointer:例如,打开一个
sqlite3
数据库会创建这样一个指针:
int sqlite3_open(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb /* OUT: SQLite db handle */
);
ie the second parameter is a pointer to pointer.即第二个参数是指向指针的指针。 This function will create the structure and give its address to us - no need to know the exact layout of the the structur at all.
这个 function 将创建结构并将其地址提供给我们——根本不需要知道结构的确切布局。
Later, we only need the address of this structure to be able to use further functionality, ie:之后,我们只需要这个结构的地址就可以使用更多的功能,即:
int sqlite3_close(sqlite3*);
The type-safety is something ensured by the compiler, once we have the machine code, the gloves are off and we can pass anything instead of sqlite3*
to the function, but we have to ensure that it would work.类型安全是由编译器确保的,一旦我们有了机器代码,手套就脱掉了,我们可以将任何东西而不是
sqlite3*
传递给 function,但我们必须确保它能工作。 Any pointer can be replaced by void*
as long as it points to a valid memory (ie with correct memory layout).任何指针都可以用
void*
替换,只要它指向有效的 memory(即具有正确的 memory 布局)。 That leads to:这导致:
import ctypes
libsqlite3 = ctypes.CDLL("libsqlite3.dylib")
sqlite3_handle = ctypes.c_void_p() # nullptr
# pass handle by reference:
res = libsqlite3.sqlite3_open(b"mydb.db", ctypes.byref(sqlite3_handle))
print("open result", res) # check res == 0
print("pointer value:", sqlite3_handle) # address is set
# do what ever needed...
# example usage of handle:
res = libsqlite3.sqlite3_close(sqlite3_handle)
print("close result", res)# check res == 0
sqlite3_handle = None # make sure nobody accesses dangling pointer
This is somewhat quick and dirty: usually one needs to set argument-types and return-value-type.这有点快速和肮脏:通常需要设置参数类型和返回值类型。 But in the functions above, defaults get correct behavior so I've skipped this (otherwise important) step.
但是在上面的函数中,默认值会得到正确的行为,所以我跳过了这个(否则很重要)步骤。
Based on ead's answer , this is a more complete example of how to use libsqlite3 from Python, which is also at https://gist.github.com/michalc/a3147997e21665896836e0f4157975cb根据ead 的回答,这是一个更完整的示例,说明如何使用 Python 中的 libsqlite3,该示例也在https://gist.github.com/michalc/a3147997e21665896836e0f4157975cb
The below defines a (generator) function, query
下面定义一个(生成器)function,
query
from contextlib import contextmanager
from collections import namedtuple
from ctypes import cdll, byref, string_at, c_char_p, c_int, c_double, c_int64, c_void_p
from sys import platform
def query(db_file, sql, params=()):
libsqlite3 = cdll.LoadLibrary({'linux': 'libsqlite3.so', 'darwin': 'libsqlite3.dylib'}[platform])
libsqlite3.sqlite3_errstr.restype = c_char_p
libsqlite3.sqlite3_errmsg.restype = c_char_p
libsqlite3.sqlite3_column_name.restype = c_char_p
libsqlite3.sqlite3_column_double.restype = c_double
libsqlite3.sqlite3_column_int64.restype = c_int64
libsqlite3.sqlite3_column_blob.restype = c_void_p
libsqlite3.sqlite3_column_bytes.restype = c_int64
SQLITE_ROW = 100
SQLITE_DONE = 101
SQLITE_TRANSIENT = -1
SQLITE_OPEN_READWRITE = 0x00000002
bind = {
type(0): libsqlite3.sqlite3_bind_int64,
type(0.0): libsqlite3.sqlite3_bind_double,
type(''): lambda pp_stmt, i, value: libsqlite3.sqlite3_bind_text(pp_stmt, i, value.encode('utf-8'), len(value.encode('utf-8')), SQLITE_TRANSIENT),
type(b''): lambda pp_stmt, i, value: libsqlite3.sqlite3_bind_blob(pp_stmt, i, value, len(value), SQLITE_TRANSIENT),
type(None): lambda pp_stmt, i, _: libsqlite3.sqlite3_bind_null(pp_stmt, i),
}
extract = {
1: libsqlite3.sqlite3_column_int64,
2: libsqlite3.sqlite3_column_double,
3: lambda pp_stmt, i: string_at(
libsqlite3.sqlite3_column_blob(pp_stmt, i),
libsqlite3.sqlite3_column_bytes(pp_stmt, i),
).decode(),
4: lambda pp_stmt, i: string_at(
libsqlite3.sqlite3_column_blob(pp_stmt, i),
libsqlite3.sqlite3_column_bytes(pp_stmt, i),
),
5: lambda pp_stmt, i: None,
}
def run(func, *args):
res = func(*args)
if res != 0:
raise Exception(libsqlite3.sqlite3_errstr(res).decode())
def run_with_db(db, func, *args):
if func(*args) != 0:
raise Exception(libsqlite3.sqlite3_errmsg(db).decode())
@contextmanager
def get_db(db_file):
db = c_void_p()
run(libsqlite3.sqlite3_open_v2, db_file.encode(), byref(db), SQLITE_OPEN_READWRITE, None)
try:
yield db
finally:
run_with_db(db, libsqlite3.sqlite3_close, db)
@contextmanager
def get_pp_stmt(db, sql):
pp_stmt = c_void_p()
run_with_db(db, libsqlite3.sqlite3_prepare_v3, db, sql.encode(), -1, 0, byref(pp_stmt), None)
try:
yield pp_stmt
finally:
run_with_db(db, libsqlite3.sqlite3_finalize, pp_stmt)
with \
get_db(db_file) as db, \
get_pp_stmt(db, sql) as pp_stmt:
for i, param in enumerate(params):
run_with_db(db, bind[type(param)], pp_stmt, i + 1, param)
row_constructor = namedtuple('Row', (
libsqlite3.sqlite3_column_name(pp_stmt, i).decode()
for i in range(0, libsqlite3.sqlite3_column_count(pp_stmt))
))
while True:
res = libsqlite3.sqlite3_step(pp_stmt)
if res == SQLITE_DONE:
break
if res != SQLITE_ROW:
raise Exception(libsqlite3.sqlite3_errstr(res).decode())
yield row_constructor(*(
extract[libsqlite3.sqlite3_column_type(pp_stmt, i)](pp_stmt, i)
for i in range(0, len(row_constructor._fields))
))
which can be used as, for example:可以用作,例如:
for row in query('my.db', 'SELECT * FROM my_table WHERE a = ?;', ('b',)):
print(row)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.