You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何修正Python 3.12+中sqlite3存储datetime的DeprecationWarning并保持数据库存储为timestamp类型

如何修正Python 3.12+中sqlite3存储datetime的DeprecationWarning并保持数据库存储为timestamp类型

我完全懂你现在的困扰——Python 3.12之后,sqlite3的默认datetime适配器被标为弃用,你照着官方文档加了适配器代码,结果发现数据库里存的不再是预期的timestamp格式,变成了秒级精度的整数,精度还降了。咱们先理清问题出在哪,再给你一个能解决所有问题的方案。

问题根源

你注册了两个针对datetime类型的适配器:adapt_datetime_iso(转成ISO字符串)和adapt_datetime_epoch(转成Unix时间戳整数)。sqlite3会使用最后注册的那个适配器,所以你的代码实际上是把datetime转成了秒级整数存在数据库里,这就是为什么你看到的是整数而非timestamp格式,而且精度只有1秒。

另外,sqlite本身并没有真正的timestamp数据类型,它属于"亲和类型"——本质上可以是TEXT、REAL或INTEGER,但我们通常用ISO格式的TEXT来存储,既保持可读性,又能保留微秒级精度,同时和Python的datetime完美兼容。

修正方案

我们需要只注册合适的适配器和转换器,确保datetime被转成ISO 8601格式的字符串(带微秒)存入数据库,同时在读取时能自动转成Python的datetime对象,彻底消除弃用警告,还能保持数据库里的"timestamp"可读性。

以下是修正后的完整代码,我会逐一标注关键改动:

from datetime import datetime, date, timedelta, time
import sqlite3
import os

# 注册适配器:将Python日期时间类型转成sqlite兼容的格式
def adapt_date_iso(val):
    """适配datetime.date为ISO 8601日期字符串"""
    return val.isoformat()

def adapt_datetime_iso(val):
    """适配datetime.datetime为带微秒的时区无关ISO 8601字符串"""
    return val.isoformat()

# 只注册ISO格式的适配器,不要重复注册epoch类型的!
sqlite3.register_adapter(date, adapt_date_iso)
sqlite3.register_adapter(datetime, adapt_datetime_iso)

# 注册转换器:将sqlite存储的字符串转成Python日期时间类型
def convert_date(val):
    """将ISO 8601日期字符串转成datetime.date对象"""
    return date.fromisoformat(val.decode())

def convert_datetime(val):
    """将ISO 8601 datetime字符串转成datetime.datetime对象"""
    return datetime.fromisoformat(val.decode())

# 为sqlite的timestamp类型注册转换器,对应我们存入的ISO字符串
sqlite3.register_converter("date", convert_date)
sqlite3.register_converter("timestamp", convert_datetime)

def main():
    script_dir = os.path.abspath(os.path.dirname(__file__))
    database_file = os.path.join(script_dir, "sqlite.db")
    print(database_file)

    # 连接数据库时必须指定detect_types,否则转换器不会生效
    con = sqlite3.connect(
        database_file,
        detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
    )
    cur = con.cursor()

    cur.execute("""CREATE TABLE if not exists timstmp (DateCrt timestamp, DateSupp timestamp)""")
    con.commit()

    cur.execute("SELECT * FROM timstmp")
    print(cur.fetchall())
    print(datetime.now())

    # 注意:这里不要重复创建连接,或者确保新连接同样指定detect_types
    # 修正你原代码里的语法错误(逗号位置不对)
    cur.execute("INSERT into timstmp values (?, ?)", (datetime.now(), None))
    con.commit()

    cur.execute("SELECT * FROM timstmp")
    print(cur.fetchall())

    con.close()

if __name__ == '__main__':
    main()

关键要点说明

  1. 单一适配器:只注册adapt_datetime_iso作为datetime的适配器,这样存入数据库的是带微秒的ISO字符串(比如2024-05-20T14:30:45.123456),在sqlite里会被识别为timestamp亲和类型,可读性和精度都有保障。
  2. 连接参数必须指定detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES是让sqlite根据列的声明类型(比如timestamp)自动调用对应的转换器,这样取出来的结果直接是Python的datetime对象,不需要手动转换。
  3. 语法修正:你原代码里第二次创建连接时语法错误(逗号放在括号外),我已经修正为复用同一个连接(或者如果需要新连接,也要加上detect_types参数)。

验证效果

  • 运行代码不会再出现DeprecationWarning
  • 打开sqlite数据库查看timstmp表,DateCrt列的值是带微秒的ISO格式字符串,属于sqlite的timestamp亲和类型
  • 从数据库读取时,直接得到Python的datetime对象,精度保留到微秒

内容来源于stack exchange

火山引擎 最新活动