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 教程。