ZetCode

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。

basic_adapter.py
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 中。

class_adapter.py
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。

multi_type_adapter.py
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。

json_adapter.py
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 类型以进行二进制存储。

blob_adapter.py
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 具有安全隐患 - 仅反序列化来自受信任来源的数据。

具有数据库特定格式的适配器

此示例演示了一个适配器,该适配器专门为数据库存储格式化数据,包括正确的转义。

db_format_adapter.py
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 类型之间进行转换,并进行验证。

type_conversion_adapter.py
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。

这种方法将类型安全与数据库存储的自动转换相结合。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程