如何修正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()
关键要点说明
- 单一适配器:只注册
adapt_datetime_iso作为datetime的适配器,这样存入数据库的是带微秒的ISO字符串(比如2024-05-20T14:30:45.123456),在sqlite里会被识别为timestamp亲和类型,可读性和精度都有保障。 - 连接参数必须指定:
detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES是让sqlite根据列的声明类型(比如timestamp)自动调用对应的转换器,这样取出来的结果直接是Python的datetime对象,不需要手动转换。 - 语法修正:你原代码里第二次创建连接时语法错误(逗号放在括号外),我已经修正为复用同一个连接(或者如果需要新连接,也要加上detect_types参数)。
验证效果
- 运行代码不会再出现
DeprecationWarning - 打开sqlite数据库查看
timstmp表,DateCrt列的值是带微秒的ISO格式字符串,属于sqlite的timestamp亲和类型 - 从数据库读取时,直接得到Python的datetime对象,精度保留到微秒
内容来源于stack exchange




