Python sqlite3.adapt 函数
上次修改时间:2025 年 4 月 15 日
本综合指南探讨了 Python 的 sqlite3.adapt 函数,该函数可以为 SQLite 数据库启用自定义类型适配。我们将介绍基本用法、注册模式和实际示例。
基本定义
sqlite3.adapt 函数注册适配器可调用对象,以将 Python 对象转换为与 SQLite 兼容的类型。 它是 SQLite 类型系统的一部分。
主要特征:它能够在 SQLite 中存储自定义 Python 类型,适用于类和可调用对象,并与 DB-API 2.0 接口集成。
基本类型适配
这是一个简单的示例,展示了如何使用 sqlite3.adapt 适配自定义 Python 类型以存储在 SQLite 中。
import sqlite3
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def adapt_point(point):
return f"{point.x};{point.y}".encode('utf-8')
# Register the adapter
sqlite3.register_adapter(Point, adapt_point)
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE points (p BLOB)")
conn.execute("INSERT INTO points VALUES (?)", (Point(4, 5),))
row = conn.execute("SELECT p FROM points").fetchone()
print(row[0]) # b'4;5'
此示例创建一个 Point 类并注册一个适配器函数,该函数将 Point 对象转换为字节。 存储 Points 时会自动调用适配器。
适配器必须返回 SQLite 原生支持的类型:None、int、float、str 或 bytes。 在这里,我们返回 bytes 用于 blob 存储。
适配复数
此示例演示了如何适配 Python 内置的复数以进行 SQLite 存储。
import sqlite3
def adapt_complex(c):
return f"{c.real},{c.imag}".encode('utf-8')
sqlite3.register_adapter(complex, adapt_complex)
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE complex_nums (num BLOB)")
conn.execute("INSERT INTO complex_nums VALUES (?)", (3+4j,))
row = conn.execute("SELECT num FROM complex_nums").fetchone()
print(row[0]) # b'3.0,4.0'
在这里,我们将 Python 的复数适配为字节字符串格式。 适配器将实部和虚部都转换为字符串表示形式。
这种方法允许在 SQLite 中存储复数,但查询它们需要在检索时进行额外的转换逻辑。
适配十进制数
此示例显示了如何正确适配 Decimal 数字以进行精确存储。
import sqlite3
from decimal import Decimal
def adapt_decimal(d):
return str(d)
sqlite3.register_adapter(Decimal, adapt_decimal)
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE prices (amount TEXT)")
price = Decimal('19.99')
conn.execute("INSERT INTO prices VALUES (?)", (price,))
row = conn.execute("SELECT amount FROM prices").fetchone()
print(Decimal(row[0])) # 19.99
Decimal 数字需要小心处理以保持精度。 此适配器将它们转换为字符串,以便在 SQLite TEXT 列中进行精确存储。
检索时,您需要手动转换回 Decimal,如 print 语句所示。 这保留了完整的十进制精度。
适配具有属性的自定义对象
此示例将具有多个属性的自定义对象适配为 JSON 字符串。
import sqlite3
import json
class Product:
def __init__(self, id, name, price):
self.id = id
self.name = name
self.price = price
def adapt_product(product):
return json.dumps({
'id': product.id,
'name': product.name,
'price': product.price
})
sqlite3.register_adapter(Product, adapt_product)
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE products (data TEXT)")
p = Product(101, "Widget", 9.99)
conn.execute("INSERT INTO products VALUES (?)", (p,))
row = conn.execute("SELECT data FROM products").fetchone()
print(row[0]) # JSON string
这种方法将复杂对象序列化为 JSON 字符串,以实现灵活存储。 JSON 格式保留了对象结构,并允许使用 SQLite 的 JSON 函数查询特定字段。
请注意,SQLite 必须编译 JSON 支持,才能对存储的 JSON 数据进行高级查询。
适配 Datetime 对象
此示例将 Python datetime 对象适配为 ISO 格式字符串。
import sqlite3
from datetime import datetime
def adapt_datetime(dt):
return dt.isoformat()
sqlite3.register_adapter(datetime, adapt_datetime)
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE events (time TEXT)")
now = datetime.now()
conn.execute("INSERT INTO events VALUES (?)", (now,))
row = conn.execute("SELECT time FROM events").fetchone()
print(row[0]) # ISO formatted datetime string
ISO 格式非常适合 datetime 存储,因为它既易于阅读又可排序。 此适配器将 datetime 对象转换为此标准格式的字符串。
对于有时区意识的日期时间,您可能希望在适配器中包含 UTC 偏移信息,以便完全保留时区。
使用类方法进行适配
此示例演示了如何使用类方法作为适配器,以实现更简洁的代码组织。
import sqlite3
class Color:
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b
@classmethod
def adapt(cls, color):
return f"{color.r},{color.g},{color.b}"
sqlite3.register_adapter(Color, Color.adapt)
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE colors (value TEXT)")
red = Color(255, 0, 0)
conn.execute("INSERT INTO colors VALUES (?)", (red,))
row = conn.execute("SELECT value FROM colors").fetchone()
print(row[0]) # '255,0,0'
使用类方法作为适配器使转换逻辑与它适配的类紧密结合。 这提高了代码组织和可维护性。
类方法可以访问所有类属性和方法,从而在需要时允许更复杂的适配逻辑。
结合适配器和转换器
这个完整的例子展示了如何适配 Python 对象进行存储,以及如何将 SQLite 值转换回 Python 对象。
import sqlite3
class Measurement:
def __init__(self, value, unit):
self.value = value
self.unit = unit
def __repr__(self):
return f"Measurement({self.value}, '{self.unit}')"
def adapt_measurement(m):
return f"{m.value}|{m.unit}".encode('utf-8')
def convert_measurement(s):
value, unit = s.decode('utf-8').split('|')
return Measurement(float(value), unit)
sqlite3.register_adapter(Measurement, adapt_measurement)
sqlite3.register_converter("MEASUREMENT", convert_measurement)
with sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES) as conn:
conn.execute("CREATE TABLE data (m MEASUREMENT)")
m = Measurement(42.5, "cm")
conn.execute("INSERT INTO data VALUES (?)", (m,))
row = conn.execute("SELECT m FROM data").fetchone()
print(row[0]) # Measurement(42.5, 'cm')
这个完整的解决方案展示了双向转换。 适配器处理存储,而转换器在检索数据时重建对象。
请注意 detect_types=sqlite3.PARSE_DECLTYPES 参数,该参数根据列类型声明启用转换器功能。
最佳实践
- 选择适当的格式: 使用保留所有必要数据的格式
- 考虑查询需求: 适配支持您的查询模式的格式
- 记录适配: 清楚地记录任何自定义类型适配
- 彻底测试: 验证往返转换是否正常工作
- 处理边缘情况: 确保适配器适用于 null/边缘值
资料来源
作者
列出所有 Python 教程。