Python sqlite3.register_adapter 函数
上次修改时间:2025 年 4 月 15 日
本综合指南探讨了 Python 的 sqlite3.register_adapter 函数,该函数使自定义 Python 类型能够存储在 SQLite 数据库中。
基本定义
sqlite3.register_adapter 函数注册一个可调用对象,用于将 Python 对象适配为与 SQLite 兼容的类型。 它将自定义 Python 类型转换为 SQLite 支持的类型之一(TEXT、INTEGER、REAL、BLOB 或 NULL)。
主要特点:它接受一个 Python 类型和一个适配器函数,全局适用于所有连接,并能够无缝存储自定义对象。 适配器函数必须返回兼容的 SQLite 类型。
基本适配器注册
此示例显示如何为自定义类注册一个简单的适配器,以将其存储为 SQLite 中的 TEXT。
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}"
# Register the adapter
sqlite3.register_adapter(Point, adapt_point)
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE points (id INTEGER PRIMARY KEY, coord TEXT)")
p = Point(10, 20)
conn.execute("INSERT INTO points (coord) VALUES (?)", (p,))
row = conn.execute("SELECT coord FROM points").fetchone()
print(row[0]) # Output: "10;20"
适配器在存储到 SQLite 时将 Point 对象转换为字符串格式。 当插入 Point 对象时,转换会自动发生。
请注意,我们不需要手动调用适配器函数 - SQLite 会自动处理已注册的类型。
具有自定义类的适配器
此示例演示了如何调整更复杂的自定义类以存储在 SQLite 中。
import sqlite3
from datetime import datetime
class Event:
def __init__(self, name, date, attendees):
self.name = name
self.date = date
self.attendees = attendees
def adapt_event(event):
return f"{event.name}|{event.date.isoformat()}|{','.join(event.attendees)}"
sqlite3.register_adapter(Event, adapt_event)
with sqlite3.connect("events.db") as conn:
conn.execute("""CREATE TABLE IF NOT EXISTS events
(id INTEGER PRIMARY KEY, details TEXT)""")
meeting = Event("Team Meeting", datetime(2023, 6, 15), ["Alice", "Bob"])
conn.execute("INSERT INTO events (details) VALUES (?)", (meeting,))
conn.commit()
row = conn.execute("SELECT details FROM events").fetchone()
print(row[0]) # Output: "Team Meeting|2023-06-15T00:00:00|Alice,Bob"
Event 对象转换为以管道分隔的字符串,其中包含其所有属性。 datetime 转换为 ISO 格式,以便进行一致的存储。
此模式允许存储复杂的对象,同时保持数据库中人类可读的格式。
适配器返回不同的 SQLite 类型
适配器可以根据对象的状态返回不同的 SQLite 类型。 此示例显示了一个适配器,该适配器返回 TEXT 或 INTEGER。
import sqlite3
class Measurement:
def __init__(self, value, is_numeric):
self.value = value
self.is_numeric = is_numeric
def adapt_measurement(m):
return m.value if m.is_numeric else str(m.value)
sqlite3.register_adapter(Measurement, adapt_measurement)
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE data (num_val INTEGER, text_val TEXT)")
m1 = Measurement(42, True)
m2 = Measurement("High", False)
conn.execute("INSERT INTO data VALUES (?, NULL)", (m1,))
conn.execute("INSERT INTO data VALUES (NULL, ?)", (m2,))
for row in conn.execute("SELECT * FROM data"):
print(row) # Output: (42, None) and (None, 'High')
适配器检查 is_numeric 标志以确定是将值原样返回(对于 INTEGER)还是作为字符串返回(对于 TEXT)。
这种灵活性允许根据上下文将相同的 Python 类型调整为不同的 SQLite 类型。
具有 JSON 序列化的适配器
对于复杂的对象,JSON 提供了一种方便的序列化格式。 此示例在适配器中使用 json.dumps。
import sqlite3
import json
class Product:
def __init__(self, id, name, specs):
self.id = id
self.name = name
self.specs = specs
def adapt_product(product):
return json.dumps({
"id": product.id,
"name": product.name,
"specs": product.specs
})
sqlite3.register_adapter(Product, adapt_product)
with sqlite3.connect("products.db") as conn:
conn.execute("CREATE TABLE products (data TEXT)")
p = Product(101, "Laptop", {"cpu": "i7", "ram": "16GB"})
conn.execute("INSERT INTO products VALUES (?)", (p,))
row = conn.execute("SELECT data FROM products").fetchone()
print(row[0]) # Output: JSON string
适配器将 Product 对象转换为 JSON 字符串,其中包含其所有属性。 这种方法可以很好地处理嵌套结构。
当对象结构可能更改或包含复杂的嵌套数据时,JSON 特别有用。
二进制数据的适配器
此示例显示如何将对象调整为 SQLite BLOB 类型以进行二进制存储。
import sqlite3
import pickle
class BinaryData:
def __init__(self, data):
self.data = data
def adapt_binary_data(bd):
return pickle.dumps(bd.data)
sqlite3.register_adapter(BinaryData, adapt_binary_data)
with sqlite3.connect("binary.db") as conn:
conn.execute("CREATE TABLE binary_store (id INTEGER PRIMARY KEY, data BLOB)")
bd = BinaryData([1, 2, 3, 4, 5])
conn.execute("INSERT INTO binary_store (data) VALUES (?)", (bd,))
row = conn.execute("SELECT data FROM binary_store").fetchone()
loaded = pickle.loads(row[0])
print(loaded) # Output: [1, 2, 3, 4, 5]
适配器使用 pickle 将数据序列化为字节,SQLite 将其存储为 BLOB。 这对于任意 Python 对象很有用。
请注意,pickle 具有安全隐患 - 仅反序列化来自受信任来源的数据。
具有数据库特定格式的适配器
此示例演示了一个适配器,该适配器专门为数据库存储格式化数据,包括正确的转义。
import sqlite3
import re
class SQLFormattedText:
def __init__(self, text):
self.text = text
def adapt_sql_text(st):
# Escape single quotes by doubling them for SQL
escaped = st.text.replace("'", "''")
# Remove control characters
cleaned = re.sub(r'[\x00-\x1f\x7f]', '', escaped)
return cleaned
sqlite3.register_adapter(SQLFormattedText, adapt_sql_text)
with sqlite3.connect("text.db") as conn:
conn.execute("CREATE TABLE documents (content TEXT)")
text = SQLFormattedText("Don't forget to escape this!")
conn.execute("INSERT INTO documents VALUES (?)", (text,))
row = conn.execute("SELECT content FROM documents").fetchone()
print(row[0]) # Output: Don''t forget to escape this!
适配器通过转义引号并删除控制字符来执行 SQL 特定的格式化。 这使数据对于 SQL 插入是安全的。
当您需要确保数据库安全的格式化,同时保持原始对象干净时,此模式很有用。
具有类型转换的适配器
此示例显示了一个适配器,该适配器在 Python 和 SQLite 类型之间进行转换,并进行验证。
import sqlite3
from decimal import Decimal
class Money:
def __init__(self, amount, currency):
if not isinstance(amount, Decimal):
raise ValueError("Amount must be Decimal")
self.amount = amount
self.currency = currency
def adapt_money(m):
if m.currency not in ("USD", "EUR", "GBP"):
raise ValueError("Unsupported currency")
return float(m.amount)
sqlite3.register_adapter(Money, adapt_money)
with sqlite3.connect("finance.db") as conn:
conn.execute("CREATE TABLE transactions (amount REAL, currency TEXT)")
try:
salary = Money(Decimal("2500.50"), "USD")
conn.execute("INSERT INTO transactions VALUES (?, ?)",
(salary, salary.currency))
for row in conn.execute("SELECT * FROM transactions"):
print(row) # Output: (2500.5, 'USD')
except ValueError as e:
print("Error:", e)
适配器在转换之前验证 Money 对象,确保仅存储有效值。 Decimal 金额转换为 float 以用于 SQLite REAL。
这种方法将类型安全与数据库存储的自动转换相结合。
最佳实践
- 保持适配器简单: 仅关注类型转换
- 验证输入: 确保对象处于有效状态
- 使用标准格式: 例如,日期的 ISO 格式,复杂数据的 JSON 格式
- 考虑性能: 复杂的适配器可能会影响速度
- 记录行为: 清楚地记录适配器的作用
资料来源
作者
列出所有 Python 教程。