ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

cursor.description 到底长啥样:TaoToken 统一 Key 下抓一次真实返回结构

cursor.description 到底长啥样:TaoToken 统一 Key 下抓一次真实返回结构 1. 先搞清楚 cursor.description 到底返回了什么cursor.description是 Python DB-API 2.0 规范里定义的一个只读属性它在你执行完一条 SELECT 语句之后才会被填充。如果你刚创建 cursor 还没 execute或者执行的是 INSERT/UPDATE 这类不返回结果集的语句它的值就是None。这个细节很多人第一次踩坑时都会懵明明代码没写错为什么打印出来是 None原因就是查询还没产生结果集。它的真实形态是「元组套元组」——外层是一个元组里面每个元素对应结果集中的一列每个元素本身又是一个 7 元组。这 7 个位置按 DB-API 规范固定为列名、类型代码、显示宽度、内部长度、精度、小数位数、是否允许为空。实际用起来99% 的场景你只需要第 0 位拿列名偶尔做类型映射时会看第 1 位的类型代码。这篇会带你在 TaoToken 统一 Key/API 通道下用本地脚本真实连一次数据库把cursor.description打印出来对比不同驱动返回的差异并给出可直接复制的连接骨架和断言验证动作。适合正在写数据导出、动态表头、ORM 底层封装或者单纯想确认字段名和类型码的 Python 开发者。2. TaoToken 前置准备统一 Key 与接入信息TaoToken 在这里的角色是统一入口你不需要为每个模型或每个环境维护一堆散落的 Key而是用一套 Key 走 API 通道。对于本篇这种「本地脚本连数据库 打印元信息」的场景它主要解决的是环境变量管理和接入地址统一的问题让脚本里的配置骨架更干净。你需要先拿到一个可用的 API Key。进入控制台创建控制台入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_descriptionAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_descriptionAPI 基础地址统一用https://taotoken.net/api这个不加 UTM。如果你后面要接 Claude Code 或做长期编码任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_description注意本篇的重点是数据库驱动的cursor.description行为TaoToken 负责的是 Key 与接入通道的统一。不要把两者混为一谈——数据库连接串仍然是你自己的数据库地址。把 Key 写进环境变量别硬编码在脚本里export TAOTOKEN_API_KEY你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api3. 可复制配置连接骨架与打印脚本下面这段是完整可运行的骨架。我用 SQLite 做演示因为它零依赖、开箱即用cursor.description的行为和 MySQL、PostgreSQL 驱动一致方便你先跑通再换真实库。import os import sqlite3 # 1. 建一个内存库并造点数据 conn sqlite3.connect(:memory:) cur conn.cursor() cur.execute( CREATE TABLE user ( id INTEGER PRIMARY KEY, name TEXT, age INTEGER, score REAL ) ) cur.execute(INSERT INTO user (name, age, score) VALUES (Alice, 30, 95.5)) cur.execute(INSERT INTO user (name, age, score) VALUES (Bob, 25, 88.0)) conn.commit() # 2. 执行查询后再看 description cur.execute(SELECT id, name, age, score FROM user) print(原始 description) print(cur.description) # 3. 只取列名 columns [col[0] for col in cur.description] print(列名列表, columns) # 4. 逐列打印 7 个位置 for col in cur.description: print(f列名{col[0]!r} 类型码{col[1]!r} 显示宽度{col[2]!r} f内部长度{col[3]!r} 精度{col[4]!r} 小数位{col[5]!r} 可空{col[6]!r}) conn.close()跑出来你会看到类似这样的结构((id, None, None, None, None, None, None), (name, None, None, None, None, None, None), (age, None, None, None, None, None, None), (score, None, None, None, None, None, None))注意 SQLite 的类型码是None这是它和 MySQL 驱动最大的差异之一。MySQL 的pymysql会返回真实的类型码比如3对应 LONG、253对应 VAR_STRING。所以如果你写的是跨库通用代码不能假设第 1 位一定有值。换成 MySQL 的连接骨架长这样import pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, passwordyour_password, databasetest_db, charsetutf8mb4, ) cur conn.cursor() cur.execute(SELECT id, name, age FROM user) print(cur.description) columns [col[0] for col in cur.description] print(columns) conn.close()PostgreSQL 用psycopg2时description的第 1 位会返回 OID 类型码第 6 位可空通常也是None因为 PG 驱动不强制填充这一位。这就是为什么「字段名、类型码与 None 场景」要分开验证——不同驱动填的位数不一样。4. 验证请求断言与成功结果对照光打印还不够工程里更稳的做法是加断言把「预期行为」固化下来。下面这几个断言可以直接抄进你的测试或启动自检里。# 断言 1查询后 description 不为 None assert cur.description is not None, description 为 None检查是否执行了 SELECT # 断言 2列数等于 description 长度 assert len(cur.description) 4 # 断言 3每个元素都是 7 元组 for col in cur.description: assert isinstance(col, tuple) and len(col) 7, f列结构异常: {col} # 断言 4列名顺序与 SELECT 一致 assert [c[0] for c in cur.description] [id, name, age, score] # 断言 5未执行查询时 description 为 None fresh conn.cursor() assert fresh.description is None, 未 execute 时不应有 description成功结果对照表场景description 值说明刚创建 cursorNone还没执行任何语句执行 INSERT/UPDATENone不产生结果集执行 SELECT元组套元组每列一个 7 元组SQLite SELECT类型码为 None驱动不填类型码MySQL SELECT类型码为整数如 3、253PostgreSQL SELECT类型码为 OID可空位常为 None如果你想把这段逻辑接到模型侧做自动生成表头或字段说明可以用模型对话入口快速验证输出格式https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_description5. 本篇常见错排查报错一TypeError: NoneType object is not iterable原因在没执行 SELECT 的 cursor 上直接遍历description。排查动作在遍历前加if cur.description is None: raise RuntimeError(先执行 SELECT)。报错二列名取出来是乱码或空字符串原因部分驱动在别名或表达式列上返回空列名比如SELECT COUNT(*) FROM user。排查动作给表达式加别名SELECT COUNT(*) AS cnt再打印[c[0] for c in cur.description]。报错三类型码在不同库对不上原因DB-API 只规定位置不规定类型码的具体数值各驱动自定义。排查动作不要硬编码col[1] 3而是用驱动提供的常量比如pymysql.constants.FIELD_TYPE.LONG。报错四description 长度和实际列数不一致原因某些驱动对隐藏列或系统列也会返回。排查动作打印完整description逐条核对必要时用cursor.fetchall()的列数交叉验证。报错五连接超时导致 description 拿不到原因网络或数据库地址配置错误execute 阶段就抛异常了。排查动作先用最小脚本只做connect()和SELECT 1确认链路通再跑完整查询。提示排障阶段建议把description和fetchall()一起打印前者给结构后者给数据对照着看最快定位问题。6. 接入与后续动作把上面的骨架跑通后你可以按需分流需要统一 Key 和接入文档API Keys 管理 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_description 接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_description想快速验证模型对字段结构的理解模型对话 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_description长期做编码或 Agent 任务Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_descriptionClaude Code 相关接入https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_description最后留一个我常用的自检习惯每次换数据库驱动先跑一遍SELECT 1 AS a, x AS b把description打印出来确认列名、类型码、None 位都符合预期再上真实业务查询。这一步花不了两分钟但能省掉后面一堆「为什么列名对不上」的排查时间。
返回列表