ZetCode

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 中。

basic_adapt.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}".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 存储。

complex_adapt.py
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 数字以进行精确存储。

decimal_adapt.py
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 字符串。

json_adapt.py
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 格式字符串。

datetime_adapt.py
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 偏移信息,以便完全保留时区。

使用类方法进行适配

此示例演示了如何使用类方法作为适配器,以实现更简洁的代码组织。

class_method_adapt.py
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 对象。

full_conversion.py
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 参数,该参数根据列类型声明启用转换器功能。

最佳实践

资料来源

作者

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

列出所有 Python 教程